Sails Tutorial — Chapter 7

Kiran Chauhan
7 min readJul 23, 2024

This is the seventh article in the series. You can read the sixth article at this link.

In this article, we are going to add the relationship between the Contact and User model, as users can create one or many contacts within the CRM system that we are currently building. In ORM terms, we normally call it a one-to-many relationship, where one user can have many contacts.

In order to add this relation, we need to modify both User.js and Contact.js models. As per the documentation, “one” side must contain the collection attribute, and the “many” side must contain a model attribute.

Open Contact.js file and write the following code.

module.exports = {
tableName: "contacts",

attributes: {
firstName: {
type: "string",
required: true,
},
lastName: {
type: "string",
required: true,
},

createdBy: {
model: "user",
},
},
};

We added a new column or an attribute with name createdBy which makes sense as we save the user id for the created contact. We also fulfill the requirement as stated above by having model attributes on the “many” side. The value of the model is in lower-case and in singular form.

Open User.js file and write the following code.

module.exports = {
tableName: "users",

attributes: {
email: {
type: "string",
required: true,
unique: true,
},
password: {
type: "string",
required: true,
},

contacts: {
collection: "contact",
via: "createdBy",
},
},

customToJSON: function () {
// ...
},

beforeCreate: async (user, next) => {
// ...
},
};

We added contacts as a virtual attribute. Sails will not create this as a column in the database, instead it is a virtual attribute live within the model/memory only. The name of this attribute is the plural of the “many” side, which is readable as — user has many “contacts”. We also fulfill the requirement as stated above by having a collection attribute on “one” side. Also, we added via property to mention that the relationship between User and Contact is via createdBy column of contacts table. And with these changes, the relationship is established.

We now need to update the ContactsController to accept createdBy value when creating a new contact as follows in create action.

module.exports = {
index: async (req, res) => {
// ...
},

show: async (req, res) => {
// ...
},

create: async (req, res) => {
const contact = await Contact.create({
firstName: req.body.firstName,
lastName: req.body.lastName,
createdBy: req.body.createdBy,
}).fetch();

res.status(201);
res.json({ data: contact });
},

update: async (req, res) => {
// ...
},

destroy: async (req, res) => {
// ...
},
};

We are now accepting the createdBy with firstName and lastName. Open the Insomnia and call POST http://localhost:3000/contacts with the following request body. Make sure to have the valid user id for the createdBy.

{
"firstName": "Jane",
"lastName": "Doe",
"createdBy": 1
}

We should get the response as follows after successful creation of the contact.

{
"data": {
"id": 2,
"firstName": "Jane",
"lastName": "Doe",
"createdBy": 1,
}
}

Now, fetch all the contacts by calling http://localhost:3000/contacts. You should get either null for other existing contacts and 1 for recent created contact. Instead of displaying the user id, we should display the details of the user. We can fetch the details created by the user by chaining the .populate() method on .find() and passing the attribute name that we are interested to populate, which is createdBy in our example.

Open the ContactsController.js file and write the following code in the index action.

module.exports = {
index: async (req, res) => {
const contacts = await Contact.find().populate("createdBy");

res.json({ data: contacts });
},

show: async (req, res) => {
// ...
},

create: async (req, res) => {
// ...
},

update: async (req, res) => {
// ...
},

destroy: async (req, res) => {
// ...
},
};

Now, again call the http://localhost:3000/contacts in Insomnia and you should get the following output.

{
"data": [
{
"id": 1,
"firstName": "Kai",
"lastName": "Doe",
"createdBy": null,
},
{
"id": 2,
"firstName": "Jane",
"lastName": "Doe",
"createdBy": {
"id": 1,
"email": "jane@doe.com"
},
}
]
}

The first contact was created before the relationship between Contact and User models established. Hence, it returns null for the createdBy column. But, a second contact was recently created with createdBy value and with .populate() method we are now getting the id and email of the createdBy user.

The relationship is on both sides. If we are on the contacts side, we can use createdBy. But, if we are on the users side, we can use contacts. But, before this works, we need to do 2 modifications. First, we need to add the .populate() method in the index action for the contacts as follows.

module.exports = {
index: async (req, res) => {
const users = await User.find().populate("contacts");

res.json({ data: users });
},

show: async (req, res) => {
// ...
},

create: async (req, res) => {
// ...
},

update: async (req, res) => {
// ...
},

destroy: async (req, res) => {
// ...
},
};

Next, we need to update the return object of customToJSON() function as we are only returning the id and email of the user. Let’s update the return object to also return contacts as well.

module.exports = {
tableName: "users",

attributes: {
// ....
},

customToJSON: function () {
return {
id: this.id,
email: this.email,
contacts: this.contacts,
};
},

beforeCreate: async (user, next) => {
// ....
},
};

Now, let’s call the http://localhost:3000/users and we should get the output as follows.

{
"data": [
{
"id": 1,
"email": "jane@doe.com",
"contacts": [
{
"id": 2,
"firstName": "Jane",
"lastName": "Doe",
"createdBy": 1,
}
]
},
{
"id": 2,
"email": "merry@doe.com",
"contacts": []
},
]
}

I have two users and the first user has created one contact so we are also getting the details of the created contact whereas for the second user it is an empty array. And with these changes, we now have the relationship on both sides with data fetching capabilities!

Before we go further and update show action, let’s add one more column in Contact.js model, and that is updatedBy. Again this column saves the user id but the one who updates the contact.

Open Contact.js file and write the following code.

module.exports = {
tableName: "contacts",

attributes: {
firstName: {
type: "string",
required: true,
},
lastName: {
type: "string",
required: true,
},

createdBy: {
model: "user",
},
updatedBy: {
model: "user",
},
},
};

Now, we have the updatedBy column which is a reference to the User model. Back to the User model, open User.js file and write the following code.

module.exports = {
tableName: "users",

attributes: {
email: {
type: "string",
required: true,
unique: true,
},
password: {
type: "string",
required: true,
},

contacts: {
collection: "contact",
via: "createdBy",
},

contacts: {
collection: "contact",
via: "updatedBy",
},
},

customToJSON: function () {
// ...
},

beforeCreate: async (user, next) => {
// ...
},
};

I copied the same code that I wrote for the createdBy column relation but changed it to updatedBy as we should populate the value of updatedBy column. We need to update the update action to accept the updatedBy field in the request body.

Open ContactsController.js file and write the following code in update action.

module.exports = {
index: async (req, res) => {
// ...
},

show: async (req, res) => {
// ...
},

create: async (req, res) => {
// ...
},

update: async (req, res) => {
const contact = await Contact.updateOne({
id: req.params.id,
}).set({
firstName: req.body.firstName,
lastName: req.body.lastName,
updatedBy: req.body.updatedBy,
});

res.json({ data: contact });
},

destroy: async (req, res) => {
// ...
},
};

The change is quite simple as we are now accepting the updatedBy value in the request body. Let’s call PUT http://localhost:3000/2 with the following request body.

{
"firstName": "Jane",
"lastName": "Doe",
"updatedBy": 2
}

Here, I’m passing a different user id for the updatedBy than the one who has created this contact. We should get the following response after a successful update.

{
"data": {
"id": 2,
"firstName": "Jane",
"lastName": "Doe",
"createdBy": 1,
"updatedBy": 2
}
}

And the value for the updatedBy user is saved in the database. In order to fetch the details created by and updated by user from the database, we again need to chain the .populate() method but this time we need to pass updatedBy attribute in the index action of ContactsController.js file.

module.exports = {
index: async (req, res) => {
const contacts = await Contact.find().populate("createdBy").populate("updatedBy");

res.json({ data: contacts });
},

show: async (req, res) => {
// ...
},

create: async (req, res) => {
// ...
},

update: async (req, res) => {
// ...
},

destroy: async (req, res) => {
// ...
},
};

Open the Insomnia and call http://localhost:3000/contacts. We should get the following response.

{
"data": [
{
"id": 1,
"firstName": "Kai",
"lastName": "Doe",
"createdBy": null,
},
{
"id": 2,
"firstName": "Jane",
"lastName": "Doe",
"createdBy": {
"id": 1,
"email": "jane@doe.com"
},
"updatedBy": {
"id": 2,
"email": "merry@doe.com"
}
}
]
}

We are now getting the correct user details of createdBy and updatedBy fields.

Let’s also update the show action to populate both createdBy and updatedBy attributes so that when we fetch the single contact, it also returns the details of the users instead of ids.

module.exports = {
index: async (req, res) => {
// ...
},

show: async (req, res) => {
const contact = await Contact.findOne({ id: req.params.id }).populate("createdBy").populate("updatedBy");

res.json({ data: contact });
},

create: async (req, res) => {
// ...
},

update: async (req, res) => {
// ...
},

destroy: async (req, res) => {
// ...
},
};

Finally, let’s also update the show action of UsersController.js file to fetch the contact details when fetching the given user.

module.exports = {
index: async (req, res) => {
// ...
},

show: async (req, res) => {
const user = await User.findOne({ id: req.params.id }).populate("contacts");

res.json({ data: user });
},

create: async (req, res) => {
c// ...
},

update: async (req, res) => {
// ...
},

destroy: async (req, res) => {
// ...
},
};

Passing the createdBy and updatedBy in the request body is intermediate code as in future when we add the support for the authentication, we will fetch the user id from the session token.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Kiran Chauhan
Kiran Chauhan

Written by Kiran Chauhan

I design software with and for people.

No responses yet

Write a response