Define Relationships Between Tables in Sequelize
20 min read·Jan 1, 2025
In Sequelize, the definition of a relationship between models allows you to represent the associations between their underlying SQL tables and facilitate the querying and management of related data, much like foreign keys and joins.
These relationships are established through the use of built-in model methods, including hasOne, belongsTo, hasMany, and belongsToMany.
These methods are usually declared in pair and allow you to create three primary types of relationships:
- One-to-one relationships using the association of
hasOneandbelongsTo. - One-to-many relationships using the association of
hasManyandbelongsTo. - Many-to-many relationships using the association of two
belongsToMany(not covered in this lesson).
Define one-to-one relationships
In a one-to-one relationship, each record in the first table is associated with exactly one record in the second table through the use of a foreign key.
To create a one-to-one relationship between two models, you can use the hasOne and belongsTo methods of the model instances:
Source.hasOne(Target);
Target.belongsTo(Source);
Where:
Sourceis the model instance containing the primary key.Targetis the model instance containing the foreign key.
When executed, Sequelize will automatically:
-
Add a new property to the
Targetmodel namedsourceIdinferred from the name of theSourcemodel. -
Add a foreign key column with the same name to the corresponding
Targettable, which is equivalent to this SQL statement:CREATE TABLE target ( id INT AUTO_INCREMENT PRIMARY KEY, -- additional columns sourceId INT, FOREIGN KEY (sourceId) REFERENCES source(id) ON DELETE SET NULL ON UPDATE CASCADE );
Note: By default, Sequelize will set the
ON DELETEconstraint toSET NULLand theON UPDATEconstraint toCASCADE, which means that:
- If a record from the corresponding
Sourcetable is deleted, thesourceIdcolumn of thetargettable will be set toNULL.- If the primary key in the corresponding
Sourcetable is updated, the foreign key in the correspondingTargettable is also updated.
Example
Let's consider this script, that defines a one-to-one relationship between two models named User and Profile:
const { Sequelize, DataTypes } = require('sequelize');
(async () => {
try {
const database = new Sequelize(/* ... */);
await database.authenticate();
const User = database.define('user', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
}
}, {
timestamps: false
});
const Profile = database.define('profile', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
}
}, {
timestamps: false
});
User.hasOne(Profile);
Profile.belongsTo(User);
await database.sync();
await database.close();
} catch(error) {
console.error(error.toString());
}
})();
When executed, Sequelize will:
-
Add a new property named
userIdto theProfilemodel. -
Create the
userstable in the database using this SQL statement:CREATE TABLE users ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY ); -
Create the
profilestable in the database with an additional foreign key nameduserIdusing this SQL statement:CREATE TABLE profiles ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, userId INT DEFAULT NULL, KEY userId (userId), FOREIGN KEY (userId) REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE );
Enforce foreign key uniqueness
As shown in the SQL statements above, Sequelize does not automatically enforce the one-to-one relationship at the SQL level.
This means that when using the hasOne and belongsTo methods to define a one-to-one relationship, it is technically possible for a primary key to be referenced in multiple rows of the target table.
To prevent this and ensure the uniqueness of the foreign key in the target table, you can add a UNIQUE constraint on the foreign key at the SQL level through the foreignKey.unique property of the model:
Source.hasOne(Target, {
foreignKey: {
// name: 'keyName',
unique: true
}
});
Target.belongsTo(Source);
Which translates to this SQL statement:
CREATE TABLE target (
id INT AUTO_INCREMENT PRIMARY KEY,
-- additional columns
sourceId INT UNIQUE,
FOREIGN KEY (sourceId) REFERENCES source(id) ON DELETE SET NULL ON UPDATE CASCADE
);
Define one-to-many relationships
In a one-to-many relationship, a record in one table is associated with multiple records in another table.
To create a one-to-many relationship between two models, you can use the hasMany and belongsTo methods of the model instances:
Source.hasMany(Target);
Target.belongsTo(Source);
When executed, Sequelize will perform the same actions as to when defining a one-to-one relationship.
Example
Let's consider this script, that defines a one-to-many relationship between two models named User and Profile:
const { Sequelize, DataTypes } = require('sequelize');
(async () => {
try {
const database = new Sequelize(/* ... */);
await database.authenticate();
const Company = database.define('company', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true
}
}, {
timestamps: false
});
const Employee = database.define('employee', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(120),
allowNull: false
},
department: {
type: DataTypes.ENUM('Engineering', 'Human Resources', 'Marketing', 'Sales'),
allowNull: false
}
}, {
timestamps: false
});
Company.hasMany(Employee);
Employee.belongsTo(Company);
await database.sync();
await database.close();
} catch(error) {
console.error(error.toString());
}
})();
When executed, Sequelize will:
-
Add a new property named
companyIdto theEmployeemodel. -
Create the
companiestable in the database using this SQL statement:CREATE TABLE companies ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE ); -
Create the
employeestable in the database with an additional foreign key namedcompanyIdusing this SQL statement:CREATE TABLE employees ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(120) NOT NULL, department enum('Engineering','Human Resources','Marketing','Sales') NOT NULL, companyId INT DEFAULT NULL, KEY companyId (companyId), FOREIGN KEY (companyId) REFERENCES companies (id) ON DELETE SET NULL ON UPDATE CASCADE );
Define an explicit foreign key
To explicitly define the name of the foreign key that is added to the target model, you can use the foreignKey.name property:
Source.hasOne(Target, {
foreignKey: {
name: 'keyName'
}
});
Target.belongsTo(Source);
Where 'keyName' is the name of the foreign key in the target table.
Tip: Defining the
foreignKeyproperty in thetargetmodel instead of thesourcemodel will have the same effect.
Change foreign key constraints
To change the behavior of the ON DELETE and ON UPDATE constraints on the foreign key, you can respectively use the onDelete and onUpdate properties:
Source.hasOne(Target, {
onDelete: 'CONSTRAINT',
onUpdate: 'CONSTRAINT',
});
Target.belongsTo(Source);
Where 'CONSTRAINT' can be one of:
'CASCADE': Deletes the associated child records when the parent record is deleted or updates the foreign key in child records to reflect the updated primary key of the parent record.'RESTRICT': Prevents the parent record from being deleted or updated if any child records reference it.'NO ACTION': Behaves similarly to'RESTRICT'but allows deferring constraint checks until the end of the transaction (if supported by the database).'SET DEFAULT': Sets the foreign key in the child table to its default value when the parent record is deleted or updated.'SET NULL': Sets the foreign key in the child table to NULL when the parent record is deleted or updated.
Summary
Here's a summary of what you've learned in this lesson:
- One-to-one relationships are defined using the association of the
hasOneandbelongsTomodel methods. - One-to-many relationships are defined using the association of the
hasManyandbelongsTomodel methods. - Many-to-many relationships are defined using the association of the
belongsToManymodel method. - Foreign key names can be explicitly defined through the
foreignKey.nameproperty. - Foreign key uniqueness can be enforced through the
foreignKey.uniqueproperty. - Foreign key
ON DELETEandON UPDATEconstraints can be changed through theonDeleteandonUpdateproperties.
Enjoying the courses?
I've made these courses completely free so anyone can learn from them. If they've helped you and you'd like to actively support the work behind BackendBrewery, you can leave a tip:
Support BackendBrewery