49

The Background

I'm building a project with SequelizeJS, a popular ORM for NodeJS. When designing a schema, there appears to be two tactics:

  1. Create model code and use the .sync() function to automatically generate tables for your models.
  2. Create model code and write manual migrations using QueryInterface and umzug.

My understanding is that #1 is better for rapid prototyping, but that #2 is a best practice for projects that are expected to evolve over time and where production data needs to be able to survive migrations.

This question pertains to tactic #2.

The Question(s)

My tables have relationships which must be reflected through foreign keys.

  • How do I create tables with foreign key relationships with one another through the Sequelize QueryInterface?

  • What columns and helper tables are required by Sequelize? For example, it appears that specific columns such as createdAt or updatedAt are expected.

CommandZ
  • 3,333
  • 1
  • 23
  • 28
slifty
  • 13,062
  • 13
  • 71
  • 109

4 Answers4

53

How do I create tables with foreign key relationships with one another through the Sequelize QueryInterface?

The .createTable() method takes in a dictionary of columns. You can see the list of valid attributes in the documentation for .define(), specifically by looking at the [attributes.column.*] rows within the params table.

To create an attribute with a foreign key relationship, use the "references" and "referencesKey" fields:

For example, the following would create a users table, and a user_emails table which references the users table.

queryInterface.createTable('users', {
  id: {
    type: Sequelize.INTEGER,
    primaryKey: true,
    autoIncrement: true
  }
}).then(function() {
  queryInterface.createTable('user_emails', {
    userId: {
      type: Sequelize.INTEGER,
      references: { model: 'users', key: 'id' }
    }
  })
});

What columns and helper tables are required by sequelize? For example, it appears that specific columns such as createdAt or updatedAt are expected.

It appears that a standard model will expect an id, updatedAt, and createdAt column for each table.

queryInterface.createTable('users', {
  id: {
    type: Sequelize.INTEGER,
    primaryKey: true,
    autoIncrement: true
  },
  createdAt: {
    type: Sequelize.DATE
  },
  updatedAt: {
    type: Sequelize.DATE
  }
}

If you set paranoid to true on your model, you also need a deletedAt timestamp.

slifty
  • 13,062
  • 13
  • 71
  • 109
  • 9
    For people ending up here, this answer is fine but the syntax is now deprecated, use the following : _references: { model: 'users', key: 'id' }_ – PhilippeAuriach Dec 25 '15 at 14:22
  • you should also remove _referencesKey: 'id'_ ;) – PhilippeAuriach Dec 25 '15 at 16:50
  • (herp derp, that's what I get for using StackOverflow on a holiday) – slifty Dec 26 '15 at 22:43
  • 1
    How do you define references to multiple entities? So if I wanted to have it so that the "users" table has a column that references multiple rows in "user_emails" (instead of the current example where the "user_emails" row references one row in "users"). – Vinay Feb 18 '16 at 00:19
  • @Vinay I may be misunderstanding, but the nature of a foreign key is that it has to reference a primary key (there can only be one row per primary key value by definition) -- which means the referenced table would only have ONE row that contains that value. You could flip the relationship, so that user emails has the primary key, and users has the foreign key (so more than one user could point to the same email) – slifty Feb 18 '16 at 02:17
  • @slifty I see. Table architecture is not my strong suit. :-) I do remember from my database class that this is the case, so you're correct. – Vinay Feb 19 '16 at 04:29
  • 1
    @slifty thanks for the detailed answer. It helped a lot. – osifo Apr 09 '17 at 06:18
  • First link is outdated. – robsch Jul 22 '21 at 07:17
  • when i remove the column in another migration (or the down migration), do i need to explicitly remove the reference, too? – Fre Timmerman Jan 04 '22 at 09:27
  • @FreTimmerman I would ask that as a separate question! – slifty Jan 04 '22 at 18:57
19

I want to offer another more manual alternative because when using manual migrations and queryInterface I ran across the following problem: I had 2 files in the migration folder like so

migrations/create-project.js
migrations/create-projectType.js

because project had column projectTypeId it referenced projectType, which wasnt created yet due to the order of the files and this was causing an error.

I solved it by adding a foreign key constraint after creating both tables. In my case I decided to write it inside create-projectType.js:

queryInterface.createTable('project_type', {
  // table attributes ...
})
.then(() => queryInterface.addConstraint('project', ['projectTypeId'], {
  type: 'FOREIGN KEY',
  name: 'FK_projectType_project', // useful if using queryInterface.removeConstraint
  references: {
    table: 'project_type',
    field: 'id',
  },
  onDelete: 'no action',
  onUpdate: 'no action',
}))
galki
  • 8,149
  • 7
  • 50
  • 62
  • 4
    Nice! one note -- you may want to consider naming your migrations with orders to start the filename, to ensure they are processed in the desired order regardless of table name (e.g. `01-create-projecttype.js`, `02-create-project.js`). This is also important because as you edit / add tables you can keep your old migration steps in tact (e.g. `03-edit-projecttype.js`). – slifty Nov 23 '17 at 06:09
  • 1
    @slifty thank you - i actually started doing just that! hope v4 of the cli has support for multiple migration folders – galki Nov 23 '17 at 08:07
  • It helped! I was doing a silly mistake where I have defined the different type in both primary and foreign key columns and due to which I have suffered for a couple of mins. Finally worked :) – Vikash Choudhary Dec 28 '18 at 09:03
  • 1
    For anyone reading this in 2022, you should probably use sequelize-cli to create migration files for you using sequelize migration:generate --name [my_migration_name] as it automatically adds a timestamp before [my_migration_name] therefore saving you the hassle of having to order your migrations manually – Alvaro Carvalho Nov 03 '22 at 19:15
  • Thank you for this. It helped me fix a related issue (adding back a foreign key constraint). And as it has already been mentioned above, we should use sequelize-cli to create the migration files to ensure proper order. – Lucio Mollinedo May 08 '23 at 14:12
13

This is to create migration file for adding a column.

Here I want to add a column area_id in users table. Run command:

sequelize migration:create --name add-area_id-in-users

Once it gets executed creates a migration file timestamp-add-region_id-in-users in the migrations folder.

In the created migration file paste the below code:

'use strict';

module.exports = {
  up: (queryInterface, Sequelize) => {
        return Promise.all([
          queryInterface.addColumn('users', 'region_id',
            {
              type: Sequelize.UUID,
              references: {
                model: 'regions',
                key: 'id',
              },
              onUpdate: 'CASCADE',
              onDelete: 'SET NULL',
              defaultValue: null, after: 'can_maintain_system'
            }),
        ]);
      },

      down: (queryInterface, Sequelize) => {
        return Promise.all([
          queryInterface.removeColumn('users', 'region_id'),
        ]);
      }
    };

Here in the users table I am going to create a column named region_id along with type and relation/foreign key/references. That's it.

halfer
  • 19,824
  • 17
  • 99
  • 186
Shashikant Pandit
  • 2,752
  • 22
  • 29
0

So first one goes as the below solution and the second question: you need not explicitly mention createdAt and updatedAt because they are generated by the Sequelize for you.

The solution is:

queryInterface.createTable('Images', {

  //...

}).then(

  return queryInterface.addConstraint('Images', ['postId'], {

    type: 'foreign key',

    name: 'custom_fkey_images',

    references: { //Required field

      table: 'Posts',

      field: 'id'

    },

    onDelete: 'cascade',

    onUpdate: 'cascade'

  })
)
halfer
  • 19,824
  • 17
  • 99
  • 186
nagaraj
  • 29
  • 2
  • 2
    Hi, OP asked two questions and you answered no one. Please edit your answer and add some description to your solution. Make sure that you're answering OP's questions. – Marek Szkudelski Apr 17 '20 at 08:50