Hire the author: Odong Sunday

A scenario where multiple documents are related to each is know as Relationship in MongoDB. We can define a collection in MongoDB as the storage of documents. It is an equivalent of a table in the MYSQL database. A document is the basic unit of data in MongoDB. I can compare a document to a JSON object but it exist in a different format known as BSON in the database . So like JSON objects, they can have multiple properties. The Project design intends to achieve Relationship in MongoDB using Node/Express.Js

Image Source: https://cdn.comparitech.com/wp-content/uploads/2019/05/Relational-Database-Management-Systems-Guide.jpg

Why would one want to query multiple collections simultaneously?

Let us consider an application where users can publish their posts(articles). In the beginning, a user needs to create an account and enter all his details. These details can include a user name, email, date of birth, password and other information. We can represent user details as a JSON object and store it as a document in a MongoDB collection. That implies that there must be a collection to keep all the documents of the different users whether he or she has a post or not. We can use Mongoose to create a collection that stores user documents in database.

Once a user has created an account, he or she can now post an article. In my opinion, an article should have at least a title, date of publishment, and name of the author. This should also be another JSON object. This implies that a separate document will be created for an article because the properties of the JSON object are different from that of the user.

In MongoDB, a collection can only store related documents. Since the user document is different from that of an article, that implies that they will be stored in different collections. It would be great to attach the name of the author to his particular article so that when other users create an account on this application and would like to read an article, they can also see the author. Yeah! That is it. We need to query these two collections with a single request and display the post and its author. Personally, I think that would be great.

Glossary

During the development, I came across certain terms used in Relationship in MongoDB . We can define them as below:

Relationship: This represents how various documents are logically related to each other.

One to many Relationship (1:N). Let us consider the case of users and articles. So, one user can have multiple articles in a scenario known as one to many relationships.

Many to one Relation (N:1). This is like the reverse of the 1:N relationship. It simply means multiple authors can write a single article.

Many to many (N: M) relationship is a relationship between two entities where both might have many relationships between each other. Let us consider an article that was written by many authors. At the same time, an author might have written many articles.

Project Setup Procedure

Enough with the introduction, let us get into some work. Please ensure to do the following to get the application running.

1. Primary Installations

I used Node/ExpressJs and MongoDB to develop this project. We, therefore, have to ensure that  Nodejs and node package manager (NPM) is installed in our machines. Please refer to the NodeJs and npm sites and follow the simple procedures to have both nodeJs and npm installed in your machine. Ensure to check the node and npm versions installed on your computer. Please ensure to have a global installation for both packages so that it is accessible in all projects on your computer.

Ensure that MongoDB is running on your application. If not, please feel free to visit their official page and follow the simple steps to have MongoDB running in your machine.

2. Project dependencies

Let us initialize some repository ( project folder) by running “npm init“ in the command line. Ok, what is initialization of a repository? This will set up a new npm package and provide us with a package.json file where we can add all the project dependencies. For the sake of this project, we will require express, body-parser, mongoose, lodash and eslint. Please ensure to have all these packages installed by running “npm install < name of the packages separated by a space>“. You can also install the packages one at a time.

npm install express bodyparser eslint mongoose lodash
Packages Installation

Enough with the installations. It’s about time we get our hands dirty with some code. For this particular application, I decided to use the model view controller (MVC) approach. The application has an app.js file as the entry point. Ensure to add all the dependencies first at the top as shown by the block of code below from line one to three below.

const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const app = express();
mongoose.connect('mongodb://localhost:27017/Posts');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
const authorRouter = require('./Views/author');
const postRouter = require('./Views/posts');
app.use('/api', authorRouter);
app.use('/api', postRouter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Application running on port ${PORT}`);
});
view raw app.js hosted with ❤ by GitHub
Entry Point of the application

Set the application to run at a particular Port. For this application, I have decided to use port 3000 (line 20). Feel free to choose any oPrimary Installationsther port in case you have another application running on port 3000. Add a connection to MongoDB as shown in line 8 of the photo above.

3. Setting up the models.

I have created the author.js and posts.js files in the model folder. A model is more like a blueprint of an object. It defines the properties of a particular object. For this reason, two separate models were designed for both the posts and the authors.

4. Setting up the controllers.

A controller is a javascript ( for this particular project) file with several functions performing various tasks. One would pass this as a call back function in case one was not using the MVC architecture. Each of these functions is responsible to perform any of the HTTP requests. I have created a separate controller for the post and the author.

5. Setting up the views.

In the views folder. There are two separate javascript files. We can use these files to provide the routes to all the HTTP verbs. For example, one would require an ID if one is to edit, delete or get one single article. In this project, I have provided a single route for all the HTTP verbs of PUT, DELETE and GET (to fetch a single item). The controller files are imported into the views files and each function is appended to its respective route. Please notice the name of the functions appended to each of the HTTP verbs in the view files and compare them to the function names exported from the controllers’ files.

This project design is to create an application that the user can create an account and then post his or her articles. We, therefore, have to display the various articles to other users. It would be great to display the name of an author on his or her article. In this project design, we are considering using single HTTP verb to ensure communication between two collections in the database in a scenario known as a database relationship.

How can I create a Post (Article)?

Great question. Let us consider this example. We are trying to create a new post and we need to attach its author to it. Let us check the demonstration code below.
We can start with the models. Check out the Post object. It has a property called “author” whose value is an object. In this object, the type is an “objectID” and is referencing the author‘s collection. This simply means, search in the author‘s collection for this particular ID and attach the author as the value of this property called “ author “.

const mongoose = require('mongoose');
const { Schema } = mongoose;
const postModel = new Schema({
title: String,
author: {
type: Schema.Types.ObjectId,
ref: 'authors',
},
date: {
type: Date,
required: true,
},
});
module.exports = mongoose.model('posts', postModel);
view raw post-model.js hosted with ❤ by GitHub
Post Model

Lets have a look at the author’s object now. It has a property called posts whose value is an array. I have decided to use an array as the value of the property since an author can have more than one post. Each value in the array is an object that has a type of ObjectID and is referencing the posts collection.

const express = require('express');
const postController = require('../controllers/post');
const postRouter = express.Router();
postRouter.param('id', postController.params);
postRouter.route('/posts')
.get(postController.get);
postRouter.route('/posts/:id')
.delete(postController.delete)
.get(postController.getOne);
postRouter.route('/author/:authorID/posts')
.post(postController.post);
postRouter.route('/authors/:authorId/posts/:postId')
.put(postController.update);
module.exports = postRouter;
view raw post-views.js hosted with ❤ by GitHub
Post Views

Notice that in the views, the ID of the author has to be added as params in the route (line 17). I have added the following lines of code in the controller. Let us check it out.

exports.post = async function (req, res) {
const authorId = await req.params.authorID;
const postObject = await req.body;
const newPost = new PostModel(postObject);
await AuthorModel.findOne({ _id: authorId }, async (err, foundAuthor) => {
if (!foundAuthor) {
return err;
}
foundAuthor.posts.push(newPost);
newPost.author = foundAuthor;
await newPost.save((error, savedPost) => {
if (error) {
return error;
}
return res.json(savedPost);
});
await foundAuthor.save((error, savedAuthor) => {
if (error) {
return error;
}
return res.json(savedAuthor);
});
return foundAuthor;
});
};
Post Controller

A Post Request for a Post (Article)

In a Post request for a specific article, we need to store the author‘s ID in a variable. We also need to store the postman ( or a frontend) request object in another variable. This is the post (Article) object. We will then pass the request object to the post model to verify the properties of the incoming object as on line 4 in the code.

Using the author-model as on the next line, we can search in the database for a particular author with the ID provided. If it fails to identify an author with the provided ID, we will receive an error message indicating that the particular author does not exist in the database. Let us say we are lucky, and this particular author exists in the database. We will add this post (push) to the author‘s array of posts and will also assign this post to this particular author as in lines 10 and 11. We can now save the post and the author as on line 12 and line 18.
The response of the created post is as below on Postman.

An Image showing the response for a single post on postman
Post response for a post(Article)

Response on Postman

The response received on Postman is showing the post and its author. One can notice that the author’s object property of the post is an entire object. The author’s object stored in the author’s collection has been attached to the post. You can also notice that the author object has a posPrimary Installationsts array that stores an author‘s posts. One can also notice the ids of the posts in that array and realize that this specific post‘s id is included in the array.
The various posts stored in the database can be displayed by calling the get request on the posts. Below is the code to fetch all the posts.

exports.get = async function (req, res) {
await PostModel.find({})
.populate('author')
.exec()
.then((posts) => {
res.json(posts);
}, (err) => {
res.send(err);
});
};
view raw get-post.js hosted with ❤ by GitHub
Get Posts

A Get Request for the list of posts

The get request will query that database and fetch all the posts in the database. The populate method will check the post object and find a property called author that is referencing to another collection ( authors collection). It will go and fetch an author with its id and fill the author’s property in the post object with the respective author from the author’s collection. This may sound confusing but when we are creating a post, we also provide an author ID in the route. This means every post is created with an author. But only the author ID will be stored in the post object. So when a post is being called, the populate method will use this particular ID to identify the author of this post.

Populate and Exec method

There is another method called exec (line 26). The populate method does not return a promise. This implies that the code below will not be executed. The exec method is added so that the rest of the codes below are executed. The response of the lists of posts can be seen as follows on Postman.

get request to fetch all the posts
Get Request Response for all Posts(Articles

Updating a post

Let us consider updating a post now. For this particular situation, I am considering updating not only the posts information but also the author of the post. This is definitely not a common scenario. Let us have a look at the code below.

exports.update = function (req, res) {
const newAuthorId = req.params.authorId;
const { postId } = req.params;
const newPost = req.body;
PostModel.findOne({ _id: postId }, (err, post) => {
if (!post) {
return err;
}
const oldAuthorID = post.author._id;
AuthorModel.findById(oldAuthorID)
.then((oldAuthor) => {
if (!oldAuthor) {
return res.status(400).send('No Author with that Particular id');
}
const index = oldAuthor.posts.indexOf(postId);
if (index > 1) {
oldAuthor.posts.splice(index, 1);
}
oldAuthor.save((error, savedAuthor) => {
if (error) {
return error;
}
return savedAuthor;
});
return oldAuthor;
});
AuthorModel.findById(newAuthorId)
.then((newAuthor) => {
if (!newAuthor) {
return err;
}
newAuthor.posts.push(post);
newAuthor.save((error, savedAuthor) => {
if (error) {
return error;
}
return savedAuthor;
});
post.author = newAuthor;
_.merge(post, newPost);
post.save((error, saved) => {
if (error) {
return error;
}
return res.json(saved);
});
return newAuthor;
});
return post;
});
};
view raw update-post.js hosted with ❤ by GitHub
Updating a Post

The request must have an id of the post and the id of the new author we want to assign to the post. We, therefore, have to search in the database using the two ids to locate both the post object and the author object. On line 6, we locate the post and identify its author (we can call this the old author, maybe!). After the “Old Author’s “ ID has been identified, we can search in the authors’ collection and identify this particular author as on line 13. In this old author objects, we will then locate his array of posts and splice this specific post from this array. Notice that we are using the post id provided in the request (line 19).

We need to save the two objects now

After splicing this particular post, we then go ahead and save the author’s object.We also now need to check if the author provided in the request exists in the database (line 35). If that specific author exists in the database, we will push this post to his array of posts (line 41) and go ahead and save this author object also.

Lastly, we need to identify this post and change its author. You guessed it right, we just need to assign the author property of the post object to the new author (line 49).

Ok. That is it. Just one more thing left. We need to change the new post information. I used lodash to achieve this. As you can notice, lodash is required at the top of the file. So at the moment, we have two objects of this post. One is stored in the database and the other is provided in the request to make the update. Below is an image of an updated post. Notice how the both IDs are provided in the URL.

A Put request response to update a post displayed on postman
Postman Response for Updating a Post

Using Merge method in Lodash to update the post

Lodash has a method called merge. This method can be used to compare two objects and merge them. The arrangement of the objects in the lodash method is very important. Notice that the old post object is on the left and the new post object (the object to update the old post or the req.body as some would call it) is on the right. So merge will compare the two objects and merge them. The object on the left will take precedence. So the properties of the left object will be compared with the properties of the right object and only update the left object with properties of the right object if they vary. Even if the right object had only one property and the left object had ten properties, only that one property will be updated on the left object (line 51).

Delete a Post

Let us now consider deleting a post. The various posts are only linked with their respective authors, this relationship can be noticed in the author’s model. When we query the Mongo database for an author’s information, the populate method in mongoose will also query the posts collection and attach the respective posts. Once we delete a particular post, the populate method will fail to identify that post when we are trying to get a particular author’s information. Let us have a look at the code below to delete a Post.

exports.delete = async function (req, res) {
await PostModel.remove((req.post), (err, removed) => {
if (err) {
res.status(400).send('post not deleted');
} else {
res.json(removed);
}
});
};
view raw delete-post.js hosted with ❤ by GitHub
Delete a Post

Querry the Author

We are almost there. Just one more thing. Lets us have a look at the author get method before we can proceed. I will just consider getting all the authors.

exports.get = async function (req, res) {
await AuthorModel.find({}).populate('posts')
.exec()
.then((authors) => {
res.json(authors);
}, (err) => {
res.send(err);
});
};
view raw get-authors.js hosted with ❤ by GitHub
Get Authors

You guessed it right. We only need to populate the post array with populate method provided by mongoose. This will query the posts collections and fill in the respectives posts of an author. Do not forget to the add exec.

Get a Post

Ok. What if we just want to display only one post. With express and mongoose, this is quite easy. Yeah! it is really easy. Let us check it out in the code below.

exports.params = async function (req, res, next, id) {
console.log(id);
await PostModel.findById(id)
.populate('author')
.exec()
.then((post) => {
if (!post) {
return res.status(400).send(' post with that Particular id');
}
req.post = post;
next();
})
.catch((err) => {
res.send(err);
});
};
view raw get-one.js hosted with ❤ by GitHub
Params method

We only need an identifier. Something unique about these posts. We are lucky again. MongoDB will provide an ID when we create a post. We only need to attach that ID when we are querying a single post. The params method above will always return a single post since the callback function is accepting an ID we are trying to locate a post with. One more thing. You noticed some strange parameter called “next”. What does it want there? An Express application is essentially a series of middleware function calls. The next middleware function in the application’s request-response cycle denoted by “next”. If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging. Check out this code below.

exports.getOne = async function (req, res) {
const post = await req.post;
res.json(post);
};
view raw post-get-one.js hosted with ❤ by GitHub
Get one post

I created the params to handle situation where one would require a single post, like in update, delete or in getting one post. To get one post we can just add the few lines of code above. Notice that it is eqauted to the return value in the params method (req.post)

Learning Tools.

There is of course of a ton of learning resources online, but I can also recommend the following that helped me achieve this taHeadingHeadingsk.
i. The MongoDB Documentations
ii. The express Documentations
iii. API design in express and MongoDB (from plural sight)
iv. Relationship in MongoDB (Check the documentation)

Learning strategy

I was mainly following the documentations to achieve most of the tasks. It was quite clear for me to follow. Once in a while, I had to use some videos on youtube ( e.g Express MongoDB REST API #7 – Relationships) to clarify certain things that I was not able to clearly understand. I had a lot of challenges with Relationship in MongoDB. Please consider searching for “Relationship in MongoDB” in case you want to achieve a related task.

Reflective Analysis

It was quite an educational process for me. I had not worked with relationship in MongoDB before. In my opinion, if one can handle the various relationships in a database using a specific language besides the CRUD operations, one can consider himself more than just an average engineer in that particular programming language. To a some extent, I can say we are able to manage Relationships in MongoDB using Node/ExpressJs.

Conclusion

Project design mainly focused on the 1:N relationship in MongoDB (i.e a single author can have multiple posts ). There are currently no tests written for this particular project. To improve this project, one can consider adding integration tests. There could be definitely more feedback on this project. Please feel free to give me feedback about this project. Please go ahead and like if you find this article useful

Find the Project Here!

Hire the author: Odong Sunday