Using the Sequelize hasOne() association method

The Sequelize hasOne() association method is used to establish an association between two defined Sequelize models.

The association method allows you to link two models so that you can retrieve data from both tables with one query execution.

Let’s see an example of creating an association in Sequelize. Suppose you have two Sequelize models representing existing tables as shown below:

const User = sequelize.define(
  "User",
  { firstName: Sequelize.STRING },
  { timestamps: false }
);

const Task = sequelize.define(
  "Task",
  { taskName: Sequelize.STRING },
  { timestamps: false }
);

The above models represent Users and Tasks tables in your SQL database server. Please note that Sequelize uses the plural forms of the model names when looking for the tables by default.

The relationship between the models is that each row in the Users table can have one Tasks row.

You need to define the relationship between the tables in Sequelize models as follows:

User.hasOne(Task);

Using the hasOne() method call, Sequelize will create an association between the Users and the Tasks tables.

The model where you call the hasOne() method is called the source model, and the model passed as the argument is the target model.

The UserId attribute will be added to the Task model.

Keep in mind that defining the relationship by calling the hasOne() method doesn’t alter the actual table and adds the foreign key constraint.

You need to call the sync() method on the Task model to let Sequelize make the necessary changes:

await Task.sync({ alter: true });

The Task.sync() call above cause Sequelize to generate and execute the following SQL statement:

ALTER TABLE `Tasks`
ADD `UserId` INTEGER,
ADD CONSTRAINT `Tasks_UserId_foreign_idx`
  FOREIGN KEY (`UserId`)
  REFERENCES `Users` (`id`)
  ON DELETE SET NULL ON UPDATE CASCADE;

Without calling the sync() method, you need to use Sequelize migration or change the actual table definitions manually.

With the column and the constraint added to the SQL table, you can perform a JOIN query in Sequelize using either a raw query or the include option:

const [results, metadata] = await sequelize.query(
  "SELECT * FROM Tasks JOIN Users ON Tasks.UserId = Users.id"
);

Learn more here: How to create JOIN queries with Sequelize

Customizing hasOne foreign key configurations

By default, Sequelize uses the primary key of the source model as the reference for the foreign key column.

You can change the foreign key reference, configurations, and constraints by specifying an options object when calling the hasOne() method.

Here’s an example of customizing the generated foreign key statement:

User.hasOne(Task, {
  foreignKey: "task_owner", // change column name
  sourceKey: "firstName", // change the referenced column
  uniqueKey: "task_user_fk", // foreign key constraint name
  onDelete: "RESTRICT", // ON DELETE config
  onUpdate: "RESTRICT", // ON UPDATE config
  constraints: false, // remove ON DELETE and ON UPDATE constraints
});

For the full list of available options, you can view the Sequelize hasOne documentation.

Making the foreign key index unique

Keep in mind that the hasOne() method creates a non-unique index on the column that’s used to save the foreign key.

The method only makes sure that the target model (the Task model in this case) has one associated data in the source model (User).

If you need to add a unique constraint to the foreign key attribute or column, then you need to add the indexes option in the target model as shown below:

const Task = sequelize.define(
  "Task",
  { taskName: Sequelize.STRING },
  {
    timestamps: false,
    indexes: [
      {
        unique: true,
        fields: ["UserId"],
      },
    ],
  }
);

await Task.sync({ alter: true });

The fields array value should define the names of the attributes/ columns where the unique constraint applies.

Once you configure the model, don’t forget to call the Task.sync({ alter: true }) again to synchronize the changes to the table.

Now the UserId attribute/ column will be unique, and inserting a duplicate value will cause SQL to throw the Duplicate entry for key error.

Now you’ve learned how the hasOne() Sequelize method works and how to configure its options.

You also learned how to create a unique index constraint for the foreign key column using the indexes option. Good work! 👍

Take your skills to the next level ⚡️

I'm sending out an occasional email with the latest tutorials on programming, web development, and statistics. Drop your email in the box below and I'll send new stuff straight into your inbox!

No spam. Unsubscribe anytime.