I have been bashing my head for days as I cannot find a valid example of async configuration in Sequelize
So as you may know, you can simply config a Sequelize instance like that
const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname')
and then declare your Model
const User = sequelize.define('User', {
// Model attributes are defined here
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING
// allowNull defaults to true
}
}, {
// Other model options go here
});
However what happens when the db credentials comes from an external service?
const credentials = await getDbCredentials();
const sequelize = new Sequelize({credentials})
since sequelize models creation are coupled with the instance creation (unlike many others ORMs) this becomes a big problem.
My current solution is the following:
const Sequelize = require("sequelize");
// Models
const { User } = require("./User");
const env = process.env.NODE_ENV || "development";
const db = {};
let sequelize = null;
const initSequelize = async () => {
if (!sequelize) {
let configWithCredentials = {};
if (env === "production") {
const credentials = await getDbCredentials();
const { password, username, dbname, engine, host, port } = credentials;
configWithCredentials = {
username,
password,
database: dbname,
host,
port,
dialect: engine,
operatorsAliases: 0
};
}
const config = {
development: {
// Dev config
},
production: configWithCredentials,
};
sequelize = new Sequelize(config[env]);
sequelize.authenticate().then(() => {
console.log("db authenticated")
});
});
}
db.User = User;
db.sequelize = sequelize;
db.Sequelize = Sequelize;
};
initSequelize().then(() => {
console.log("done");
});
module.exports = db;
However I feel that this is not a good approach because of the asynchronous nature of the initialization and sometimes the db
is undefined. Is there a better way to approach this thing? Thanks
You can achieve this with beforeConnect hook, something like this:
sequelize = new Sequelize(config.database, '', '', config);
sequelize.beforeConnect(async (config) => {
config.username = await getSecretUsername();
config.password = await getSecretPassword();
});
Leave the initial credentials empty, then use beforeConnect to mutate the config. Not sure if this is the cleanest way to use it but seems to be working.
I think your db is sometimes undefined, because in your async function you're not "waiting" for the resolution of sequelize.authenticate(). Change this:
sequelize.authenticate().then(() => {
console.log("db authenticated")
});
To this:
await sequelize.authenticate()
console.log("db authenticated")
What was happening, is that your initSequelize async function would resolve, before sequelize.authenticate promise would. This is a common pitfall in JS. I think this adjustment will solve your problem. Regarding "the best approach", i don't see much that can be done here, but of course i don't have the entire picture.
The sequelize model definition is really just a plain object so that can be set early. Model initialisation does require a sequelize
instance to be passed in.
The setup was a bit clearer to me when using ES6 class definitions for the models. sequelize.define
is replaced with a call to Model.init
, and this can all be done in an async setup function.
const Sequelize = require('sequelize')
const { Model } = Sequelize
class User extends Model {
static get modelFields(){
return {
id: {
type: Sequelize.UUID,
primaryKey: true,
defaultValue: Sequelize.UUIDV4,
},
name: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
}
}
}
static get modelOptions(){
return {
version: true,
}
}
static init(sequelize){
const options = { ...this.modelOptions, sequelize }
return super.init(this.modelFields, options)
}
static associate(models) {
this.hasMany(models.Task)
}
}
module.exports = User
const User = require('./User')
class Database {
async static setup(){
const credentials = await getCredentials()
this.sequelize = new Sequelize(credentials)
User.init(this.sequelize)
this.User = User
// When you have multiple models to associate add:
this.User.associate(this)
}
}
module.exports = Database
Due to the async credentials requirement, the rest of your app will just need to cope with the delay until the DB is setup. If this is a koa/express app for example, you could delay the server .listen()
until the Database.setup()
promise has resolved.
As this would have changed a lot of my code. I have ended up by creating a script in golang that gets my credential "asynchronously" before running my server. I have use some code from this package: https://github.com/telia-oss/aws-env
And then pass my starting script as a command argument so I could "inherit" the environmental variables ./getEnv exec -- node index.js
I found a 'pure' sequelize way to do this through lifecyclehooks :
Basically a generic setup in a db.js file would look like this:
const { Sequelize } = require('sequelize'); const asyncFetch = require('../util/async-fetch'); const sequelize = new Sequelize({ dialect: 'mysql', database: 'db_name', host: '127.0.0.1' }); sequelize.beforeConnect(async (config) => { const [username, password] = await Promise.all([ asyncFetch('username'), asyncFetch('password') ]); config.username = username; config.password = password; }); module.exports = sequelize;
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.