When querying your database in Sequelize, you'll often want data associated with a particular model which isn't in the model's table directly. This data is usually typically associated through join tables (e.g. a 'hasMany' or 'belongsToMany' association), or a foreign key (e.g. a 'hasOne' or 'belongsTo' association).
When you query, you'll receive just the rows you've looked for. With eager loading, you'll also get any associated data. For some reason, I can never remember the proper way to do eager loading when writing my Sequelize queries. I've seen others struggle with the same thing.
Eager loading is confusing because the 'include' that is uses has unfamiliar fields is set in an array rather than just an object.
So let's go through the one query that's worth memorizing to handle your eager loading.
Here's how you would find all the rows of a particular model without eager loading.
Albums.findAll()
.then(albums => console.log(albums))
.catch(console.error)
Here's how you would find all the Artists associated with your albums.
Albums.findAll({
include: [{// Notice `include` takes an ARRAY
model: Artists
}]
})
.then(albums => console.log(albums))
.catch(console.error)
This will take you a long way. Sequelize is smart enough to pull in any rows from your Artists model that are associated with your Albums.
Include takes an array of objects. These objects have properties like model
, as
, and where
which tell Sequelize how to look for associated rows.
Now, let's customize how we receive our eagerly-loaded rows.
Albums.findAll({
include: [{
model: Artists,
as: 'Singer' // specifies how we want to be able to access our joined rows on the returned data
}]
})
.then(albums => console.log(albums))
.catch(console.error)
In this query, we have specified that the instances we receive back from Sequelize should have a property called 'Singer'. We'll be able to access any rows from our Artists table associated with our Albums through this .Singer property.
Finally, let's layer a where
onto our include, so we can narrow down the rows that we'll receive.
Albums.findAll({
include: [{
model: Artists,
as: 'Singer',
where: { name: 'Al Green' } //
}]
})
.then(albums => console.log(albums))
.catch(console.error)
Our where
query should look familiar if you've used Sequelize before: it has the same format as any typical Sequelize query. Our query will now only return joined rows where 'Al Green' is the name of the associated artist. We can access Al Green's artist data for relevant Album instances on our aliased .Singer
property.
To wrap up, include
takes an array of objects. These objects are queries of their own, essentially just Sequelize queries within our main query. Inside each include query we specify the associated model
, narrow our results with where
, and alias our returned rows with as
. Memorizing model
, where
, as
, and that include takes an array will make your next experience with eager loading much more pleasant.
When using one-to-many associations you might use alias for the table or not. In case you have 1-m association you could use as alias the plural word or an object like so:
const Task = this.sequelize.define('task', { title: Sequelize.STRING })
const User = this.sequelize.define('user', { username: Sequelize.STRING })
User.hasMany(Task, {
as: 'todos'
});
User.hasMany(Task, {
as: {
singular: 'todo',
plural: 'todos'
}
});
Although sequelize will try to resolve singular/plural it by itself you can offer either plural or both variations. On the contrary, you can't do so in one-to-one relations. You always have to provide the singular version only.
Task.belongsTo(User, { as: 'author' });
The following methods are exposed on each case:
- one-to-many
getTasks
countTasks
hasTask
hasTasks
setTasks
addTask
addTasks
removeTask
removeTasks
createTask
- Creates a new Task in DB
- one-to-one
getAuthor
setAuthor
- Assigns an existing UsercreateAuthor
- Creates a new User in DB
You already declared the relations between models, now the time to save them has come. You can either use the methods exposed (described above) or schema objects to do so.
In case of one-to-many or many-to-many relations you can pass inside each method an array of models or an array of IDs. While calling an addMethod
, in case the model does not have an ID attached to it, then the model will be created otherwise an update operation will occur.
const user = new User();
// user.addTodos in case you used an alias as the example above
user.addTasks([1234, 124, 23, 4233]); // An array of task IDs
user.addTasks([taskIsntanceA, taskIsntanceB, taskIsntanceC]) // Task model instances
Another way tot do so is to pass on models create
/update
methods a json schema as second parameter. Note: If you declared the model using an alias, that alias must be used here as well. Again, if you pass the ids will just update the model to keep reference of these IDs.
const { User, Task } = sequelize.models;
const user = User.create({
username: 'dimitrk',
todos: [taskIsntanceA, taskIsntanceB, taskIsntanceC], // Using mdoel instances or even regular json objects
todoIds: [1234, 124, 23, 4233] // or passing their IDs
}, {
include: [{
model: Task,
as: 'todos',
}],
})