131

I am looking for an example nodejs app that uses the sequelize ORM.

My main concern is that it seems next to impossible to define your models in separate js files if those models have complex relationships to one another because of require() dependency loops. Maybe people define all their models in one file that is very very long?

I am mainly interested in how the models are defined and use through out the app. I would like to have some validation that what i am doing on my own is the "good" way to do things.

mkoryak
  • 57,086
  • 61
  • 201
  • 257
  • 3
    I added an example that may will help someone https://github.com/shaishab/sequelize-express-example – Shaishab Roy Aug 06 '17 at 12:34
  • I have written an article about our solution: https://medium.com/@ismayilkhayredinov/sequelize-model-loading-with-es6-ffe4f38fd9b6 – hypeJunction Mar 22 '19 at 08:27

11 Answers11

129

The short story

The trick in this case is not to initialize the model in the file but just to provide the necesary information for its initialization and let a centralized module take care of the models setup and instantiation.

So the steps are:

  • Have several Model files with data about the model, like fields, relationships and options.
  • Have a singleton module which loads all those files and setup all the model classes and relationships.
  • Setup your singleton module at the app.js file.
  • Get the model classes from the singleton module do not use require on your model files, load the models from the singleton instead.

The longer story

Here is a more detailed description of this solution with the corresponding source code:

http://jeydotc.github.io/blog/2012/10/30/EXPRESS-WITH-SEQUELIZE.html

EDIT: This is a very old answer! (read down for info)

It's old and limited in many ways!

  • First, as @jinglesthula mentioned in comments (and I experienced it too) - there are problems with requiring those files. It's because require doesn't work the same way as readdirSync!

  • Second - you are very limited in relations - the code doesn't provide options to those associations so you are UNABLE to create belongsToMany as it needs through property. You can make the most basic assocs.

  • Third - you are very limited in model relations! If you read closely the code, you will see that relations is an Object instead of an Array, so if you want to make more than one associations of the same type (like having two times belongsTo) - you cannot!

  • Fourth - You don't need that singleton thingy. Every module in nodejs is singleton by itself, so all this makes is pretty complex for no reason.

You should see Farm's answer! (The link to the article is broken, but I'll fix it with this official sample from sequelize: https://github.com/sequelize/express-example/blob/master/models/index.js - you can browse the whole project to get an idea of what's going on).

p.s. I'm editing this post as it's so upvoted that people won't even see any new answers (as I did).

Edit: Just changed the link to a copy of the same post, but in a Github Page

Community
  • 1
  • 1
user1778770
  • 1,550
  • 1
  • 11
  • 10
  • Also, I was under the impression that all `require`d modules in node were in a sense singletons because the code in them is executed once and then cached, so that next time you require them you are getting a a cached object reference. Is this not the whole picture? – mkoryak Nov 01 '12 at 14:08
  • 1
    @mkoryak, you are right - all commonjs modules in node are effectively singletons, as the returned value is cached after the first execution. http://nodejs.org/api/modules.html#modules_caching – Casey Flynn Mar 29 '13 at 07:17
  • 2
    So, the example could be simplified by removing the singleton tricky part and just put module.exports = new OrmClass(). I'll try it out, thanks for your feedback :) – user1778770 Apr 10 '13 at 02:32
  • 2
    Just in case anyone had the headache I had, I'll save you. I had issues with the code listed in the github article that centered around paths. I had to add a . to the require (like this: var object = require('.' + modelsPath + "/" + name);) and also put a return if name.indexOf('DS_Store') > -1 in the forEach in the init function (yay OSX). Hope that helps. – jinglesthula Apr 14 '13 at 00:02
  • as @jinglesthula mentioned - there are some changes/bugs in the sample for loading files withing directory (especially if it's nested somewhere else). I would also add the ability to pass options to the relations, as they are very important (like the name of the foreign key, if it's allowed to be null, etc.) – Andrey Popov Mar 12 '15 at 14:58
97

SequelizeJS has a article on their website which solves this problem.

Link is broken, but you can find the working sample project here and browse it. See edited answer above to see why this is a better solution.

Extract from article:

  • models/index.js

    The idea of this file is to configure a connection to the database and to collect all Model definitions. Once everything is in place, we will call the method associated on each of the Models. This method can be used to associate the Model with others.

          var fs        = require('fs')
            , path      = require('path')
            , Sequelize = require('sequelize')
            , lodash    = require('lodash')
            , sequelize = new Sequelize('sequelize_test', 'root', null)
            , db        = {} 
    
          fs.readdirSync(__dirname)
            .filter(function(file) {
              return (file.indexOf('.') !== 0) && (file !== 'index.js')
            })
            .forEach(function(file) {
              var model = sequelize.import(path.join(__dirname, file))
              db[model.name] = model
            })
    
          Object.keys(db).forEach(function(modelName) {
            if (db[modelName].options.hasOwnProperty('associate')) {
              db[modelName].options.associate(db)
            }
          })
    
          module.exports = lodash.extend({
            sequelize: sequelize,
            Sequelize: Sequelize
          }, db)
    
Farm
  • 3,356
  • 2
  • 31
  • 32
  • 12
    This is the way that Sequelize recommends doing it. I would accept this as the correct answer. – jpotts18 Dec 20 '13 at 19:05
  • Just fixed the broken link and added explanation to the selected question why it's not ok :) – Andrey Popov Mar 12 '15 at 16:19
  • 3
    This is good, but you can't use a model from another model's instance methods, or perhaps I missed something. – mlkmt Mar 30 '15 at 16:37
  • 1
    The page doesn't exist any more – Mike Cheel Aug 30 '15 at 00:05
  • 1
    Here's the working link: http://sequelize.readthedocs.org/en/1.7.0/articles/express/ – chrisg86 Oct 23 '15 at 19:00
  • If you see my answer below, I've created a package which handles this so people don't have to keep reimplementing this in their code. https://www.npmjs.com/package/sequelize-connect – jspizziri Apr 19 '16 at 13:49
  • 3
    @mlkmt you can! Since you have access to the `sequelize` variable in your model file, you can access your other model with `sequelize.models.modelName`. – Guilherme Sehn Oct 15 '16 at 03:37
  • it's not a very good practice to use sync io operations at runtime; am i the only one seeing this? i wouldn't recommend this solution. one alternative (albeit an expensive one, in terms of performance) is to use a proxy around the DB instance, that delegates function invocations to the relevant model (via DI). – Eliran Malka Jul 23 '18 at 20:42
  • please share working link as above both links are not working. – rohit Apr 09 '21 at 07:40
  • I found a link in the wayback machine, which kind of works, although the CSS is busted. Still, it might give you an idea. https://web.archive.org/web/20141129235215/http://sequelize.readthedocs.org/en/1.7.0/articles/express/ – allanberry Oct 28 '21 at 19:07
  • Is this answer still valid? the commented code above is what I am refering too. The links are dead but the link to this https://github.com/sequelize/express-example does not match the code above so I am curious what the correct way is? – Tanner Summers Aug 08 '22 at 15:05
29

I've create a package sequelize-connect to help people deal with this issue. It follows the Sequelize suggested convention here: http://sequelize.readthedocs.org/en/1.7.0/articles/express/

Additionally it also functions a bit more like Mongoose in terms of its interface. It allows you to specify a set of locations where your models are located and also allows you to define a custom matching function to match your model files.

The usage is basically like this:

var orm = require('sequelize-connect');

orm.discover = ["/my/model/path/1", "/path/to/models/2"];      // 1 to n paths can be specified here
orm.connect(db, user, passwd, options);                        // initialize the sequelize connection and models

Then you can access the models and sequelize like so:

var orm       = require('sequelize-connect');
var sequelize = orm.sequelize;
var Sequelize = orm.Sequelize;
var models    = orm.models;
var User      = models.User;

Hopefully this helps someone out.

jspizziri
  • 793
  • 1
  • 9
  • 24
  • 3
    Linking to an article helps a bit. Quoting some docs is better. Showing a code snippet is great.... But actually building a library that solves the problem and putting it up on NPM is **fantastic** and deserves more love! +1 and will star your project. – Stijn de Witt Sep 07 '16 at 08:06
8

I started using Sequelize in Express.js app. Soon enough ran into issues of the nature you're describing. Maybe I did not quite understand Sequelize, but to me doing things more than just selecting from one table wasn't really convenient. And where ordinarily you would use select from two or more tables, or a union in pure SQL, you would have to run separate queries, and with the async nature of Node it's just added complexity.

Therefore I moved away from using Sequelize. Moreover I am switching from using ANY data fetching from DB in the models. In my opinion it is better to abstract getting data completely. And reasons are - imagine that you don't just use MySQL (in my case, I use MySQL and MongoDB side by side), but you can get your data from any data provider and any transport method, e.g. SQL, no-SQL, filesystem, external API, FTP, SSH etc. If you tried to do all of it in the models, you would eventually create complex and hard to understand code that would be hard to upgrade and debug.

Now what you want to do is to have models get data from a layer that knows where and how to get it, but your models only use API methods, e.g. fetch, save, delete etc. And inside this layer you have specific implementations for specific data providers. E.g. you can request certain data from a PHP file on a local machine or from Facebook API or from Amazon AWS or from remote HTML document, etc.

PS some of these ideas were borrowed from Architect by Cloud9: http://events.yandex.ru/talks/300/

mvbl fst
  • 5,213
  • 8
  • 42
  • 59
  • These are valid points, but I would rather avoid reimplementing `fetch`, `save`, `delete` etc. outside of `Sequelize` given that the framework already provides the means. It is nicer, but less convenient to have a separate fetching layer. At the same time, you could probably add a fetching abstraction layer around Sequelize but then the solution is more complicated, for an arguable win. – Zorayr Sep 29 '16 at 01:06
  • this tutorial be very helpfuL: [sequelize+express example](https://github.com/sequelize/express-example) – Lucas Do Amaral Nov 18 '16 at 01:55
  • @mvbl-fst You've just described a DAO layer. Let's say you have some users in an SQL DB and different users on the filesystem. You should have two DAOs which abstract how to get each of them, then a business layer which concatenates the users together (maybe even adapts some properties) and passes them back to your route (the presentation layer). – DJDaveMark Jun 19 '18 at 09:54
5

I set it up as Farm and the documentation describe.

But I was having the additonal problem that in my instance methods and class methods that I would attach to the models in each function I would need to require the index file to get a hold of other database objects.

Solved it by making them accessible to all models.

var Config = require('../config/config');

 var fs = require('fs');
var path = require('path');
var Sequelize = require('sequelize');
var _ = require('lodash');
var sequelize;
var db = {};

var dbName, dbUsername, dbPassword, dbPort, dbHost;
// set above vars

var sequelize = new Sequelize(dbName, dbUsername, dbPassword, {
dialect: 'postgres', protocol: 'postgres', port: dbPort, logging: false, host: dbHost,
  define: {
    classMethods: {
        db: function () {
                    return db;
        },
        Sequelize: function () {
                    return Sequelize;
        }

    }
  }
});


fs.readdirSync(__dirname).filter(function(file) {
   return (file.indexOf('.') !== 0) && (file !== 'index.js');
}).forEach(function(file) {
  var model = sequelize.import(path.join(__dirname, file));
  db[model.name] = model;
});

Object.keys(db).forEach(function(modelName) {
  if ('associate' in db[modelName]) {
      db[modelName].associate(db);
  }
});

module.exports = _.extend({
  sequelize: sequelize,
  Sequelize: Sequelize
}, db);

And in the model file

var classMethods = {
  createFromParams: function (userParams) {
    var user = this.build(userParams);

    return this.db().PromoCode.find({where: {name: user.promoCode}}).then(function (code) {
        user.credits += code.credits;
                return user.save();
    });
  }

};

module.exports = function(sequelize, DataTypes) {
  return sequelize.define("User", {
  userId: DataTypes.STRING,
}, {  tableName: 'users',
    classMethods: classMethods
 });
};

I only did this for the class methods but you could also do the same thing for instance methods.

jacob
  • 2,284
  • 3
  • 20
  • 21
  • +1 for that prototype classMethod that returns the db. Exactly the idea I was looking for to be able to load classMethods during define but also be able to reference any Model in a ClassMethod (i.e. for including relationships) – bitwit Jan 29 '15 at 16:20
2

I am following the official guide: http://sequelizejs.com/heroku, which has a models folder, set up each module in separate files, and have a index file to import them and set the relationship among them.

Ron
  • 6,037
  • 4
  • 33
  • 52
2

Sample model sequelize

'use strict';
const getRole   = require('../helpers/getRole')
const library   = require('../helpers/library')
const Op        = require('sequelize').Op

module.exports = (sequelize, DataTypes) => {
  var User = sequelize.define('User', {
    AdminId: DataTypes.INTEGER,
    name: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Name must be filled !!'
        },
      }
    },
    email: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Email must be filled !!'
        },
        isUnique: function(value, next) {
          User.findAll({
            where:{
              email: value,
              id: { [Op.ne]: this.id, }
            }
          })
          .then(function(user) {
            if (user.length == 0) {
              next()
            } else {
              next('Email already used !!')
            }
          })
          .catch(function(err) {
            next(err)
          })
        }
      }
    },
    password: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Password must be filled !!'
        },
        len: {
          args: [6, 255],
          msg: 'Password at least 6 characters !!'
        }
      }
    },
    role: {
      type: DataTypes.INTEGER,
      validate: {
        customValidation: function(value, next) {
          if (value == '') {
            next('Please choose a role !!')
          } else {
            next()
          }
        }
      }
    },
    gender: {
      type: DataTypes.INTEGER,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Gender must be filled !!'
        },
      }
    },
    handphone: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Mobile no. must be filled !!'
        },
      }
    },
    address: DataTypes.TEXT,
    photo: DataTypes.STRING,
    reset_token: DataTypes.STRING,
    reset_expired: DataTypes.DATE,
    status: DataTypes.INTEGER
  }, {
    hooks: {
      beforeCreate: (user, options) => {
        user.password = library.encrypt(user.password)
      },
      beforeUpdate: (user, options) => {
        user.password = library.encrypt(user.password)
      }
    }
  });

  User.prototype.check_password = function (userPassword, callback) {
    if (library.comparePassword(userPassword, this.password)) {
      callback(true)
    }else{
      callback(false)
    }
  }

  User.prototype.getRole = function() {
    return getRole(this.role)
  }

  User.associate = function(models) {
    User.hasMany(models.Request)
  }

  return User;
};

2

What worked for me is to:

  1. Create a file for each individual model like user.model.js in folder models/user.model.js.
  2. Create index.js in models/index.js and import every model to it.
  3. Define association, run sync method in index.js and export all models.
  4. Create a database.js file that holds information about Sequalize and import it and initialize it in app.js

Example of one models/user.model.js

import { DataTypes } from 'sequelize';
import { sequelize } from '../database.js';

export const User = sequelize.define("user",{
    uid:{
      type:DataTypes.STRING,
      allowNull:false,
      unique: true
    },
    email:{
      type:DataTypes.STRING,
      allowNull:true
    },
    firstName:{
      type:DataTypes.STRING,
      allowNull:true
    },
    lastName:{
      type:DataTypes.STRING,
      allowNull:true
    },
    companyWebsite:{
      type:DataTypes.STRING,
      allowNull:true
    },
    domain:{
      type:DataTypes.STRING,
      allowNull:true
    },
    hsPortalId:{
      type:DataTypes.INTEGER,
      allowNull:true
    },
    integrations:{
      type:DataTypes.STRING
    },
    brandedKeywords : {
      type:DataTypes.STRING
    },
    companyName: {
      type:DataTypes.STRING
    },
    companyStreet:{
      type:DataTypes.STRING
    },
    companyZip:{
      type:DataTypes.STRING
    },
    companyCountry:{
      type:DataTypes.STRING
    },
    vatId:{
      type:DataTypes.STRING
    },
    brand:{
      type:DataTypes.STRING
    },
    markets:{
      type:DataTypes.JSON
    },
    niche : {
      type:DataTypes.JSON
    }
  
  },{schema:"api"})

Example of models/index.js

import { Billing } from './billing.model.js';
import { Competitor } from './competitors.model.js';
import { DemoAccount } from './demo.model.js';
import { Notification } from './notification.model.js';
import { Product } from './products.model.js';
import { Reseller } from './resellers.model.js';
import {Reseller_User} from './reseller_user.model.js'
import { Tag } from './tags.model.js';
import {User} from './user.model.js'

Reseller.belongsToMany(User, { through: Reseller_User });
User.belongsToMany(Reseller, { through: Reseller_User });

// this will create a UserId column on your Product table
// https://www.youtube.com/watch?v=HJGWu0cZUe8 40min
User.hasMany(Product,{onDelete: 'CASCADE',})
Product.belongsTo(User)

User.hasOne(DemoAccount,{onDelete: 'CASCADE',})
DemoAccount.belongsTo(User)

User.hasMany(Billing,{onDelete: 'CASCADE',})
Billing.belongsTo(User)

User.hasMany(Tag,{onDelete: 'CASCADE',})
Tag.belongsTo(User)

User.hasMany(Competitor,{onDelete: 'CASCADE'})
Competitor.belongsTo(User)

User.hasMany(Notification,{onDelete: 'CASCADE'})
Notification.belongsTo(User)


User.sync().then(
    () => console.log("Sync complete")
);

Reseller.sync().then(
() => console.log("Sync complete")
);

Reseller_User.sync().then(
() => console.log("Sync complete")
);

Product.sync().then(
() => console.log("Product Sync complete")
);

Competitor.sync().then(
() => console.log("Competitor Sync complete")
);

Notification.sync().then(
() => console.log("Competitor Sync complete")
);

Billing.sync().then(
() => console.log("Billing Sync complete")
);

Tag.sync().then(
() => console.log("Tag Sync complete")
);

DemoAccount.sync()

export { User, Reseller, Product, Competitor, Notification, DemoAccount, Billing, Tag };

// DemoAccount.sync({force:true}).then(
//   () => console.log("Sync complete")
// );
Tajs
  • 521
  • 7
  • 18
1

You can import models from other files with sequelize.import http://sequelizejs.com/documentation#models-import

That way you can have one singleton module for sequelize, which then loads all the other models.

Actually this answer is quite similar to user1778770`s answer.

  • 1
    does this work with circular dependencies? For example when model A has a FK to model B and model be has a FK to model A – mkoryak Jun 17 '13 at 00:25
1

I am looking for an example nodejs app that uses the sequelize ORM.

You might be interested in looking at the PEAN.JS boilerplate solution.

PEAN.JS is a full-stack JavaScript open-source solution, which provides a solid starting point for PostgreSQL, Node.js, Express, and AngularJS based applications.

The PEAN project is a fork of the MEAN.JS project (not to be confused with MEAN.IO or the generic MEAN stack).

PEAN replaces MongoDB and the Mongoose ORM with PostgreSQL and Sequelize. A primary benefit of the MEAN.JS project is the organization it provides to a stack that has many moving pieces.

mg1075
  • 17,985
  • 8
  • 59
  • 100
0

You can also use a dependency injection which provides an elegant solution to this. Here's one https://github.com/justmoon/reduct