简体   繁体   中英

Sequelize with asynchronous configuration in nodejs

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.

https://sequelize.org/master/manual/hooks.html

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM