Let's Do: Sequelize Associations

Published 2020-02-01

Teapot

Photo by Louis Hansel on Unsplash

Sequelize is a Node.js ORM for relational databases. Relations are represented in code with "associations".

These are the 4 types of associations, where A and B are sequelize Models:

A.hasOne(B);
A.hasMany(B);

A.belongsTo(B);
A.belongsToMany(B, {through: 'C'});  // where C is a junction table

In the above examples, Sequelize would refer to A as the source and B as the target.

The has methods define the foreign key in the target, while the belongs methods define the foreign key in the source. Note the subtle different in whether to use sourceKey or targetKey:

A.hasMany(B, {
  foreignKey: 'a_id',  // B.a_id
  sourceKey: 'id', // the A.id
});

B.belongsTo(A, {
  foreignKey: 'a_id',  // B.a_id
  targetKey: 'id', // the A.id
})

The way I remember this is by thinking of those horrible trashy "property of..." tattoos. In this metaphor, the foreign key is the tattoo.

If A belongs to B, who gets the foreignKey tattoo? A.

But instead if A has many B's, then who gets the tattoos? The B's do...

(I know, dumb metaphor 🐐)

Examples

Let's use the exmaple of a pet hospital where we have Users that own Pets that have Appointments at Hospitals. Note that the Hospital would also own Appointments.

An important thing to note when defining relationships is you need to either... a. define relationships in a single file, or b. define the relationship from only one side

Doing the below (separate files) will not work.

// THIS WON'T WORK
// user.js
User.hasMany(Pet, {
  foreignKey: 'user_id',
  sourceKey: 'id',
});

// pet.js
Pet.belongsTo(User, {
  foreignKey: 'user_id',
  targetKey: 'id',
});

I tried to do this and it seemed like it created something similar to a circular reference. It claimed the target on belongsTo() was not a subclass of Sequelize.Model (which it definitely was 🤷‍♀️).

Error: comment.belongsTo called with something that's not a subclass of Sequelize.Model

The way to get this to work is either put relationships in a single file or do only one side of the relationship. I thought the all-in-one-file approach was a little icky, so I went with only one side.

To keep things consistent, I stuck with using only the has functions since it felt more natural that way to me.

Here is what I ended up with.

// user.js
User.hasMany(Pet, {
  foreignKey: 'user_email',
  sourceKey: 'email',
});

User.hasMany(Appointment, {
  foreignKey: 'user_email',
  sourceKey: 'email',
});


// location.js
Location.hasMany(Appointment, {
  foreignKey: 'loc_id',
  sourceKey: 'id',
});
//

The only case where I could need to use any of the belongs functions is with belongsToMany, since that is the only way to define a many-to-many relationship.

If, for example, we were to include Veterinarians into the relationship that worked at a few different hospitals (do they?), then we could define that through a junction table.

// veterinarian.js
Veterinarian.belongsToMany(Location, {
  through: 'VeterinarianLocations'
});

Sequelize will automatically create the VeterinarianLocations model which will act as the junction model. The model would contain both Veterinarian primary key and Location primary key.

And that's it!

Happy coding 👩‍💻


#nodejs#sql#javascript