Part 3: Develop CRUD Operations with Node, Express, and MongoDB

Sylva Elendu
Facebook Developer Circles Lagos
10 min readJun 27, 2018

--

Everything you need to know about creating basic CRUD operations with Express and MongoDB.

Isn’t this so relaxing? Your code should be too. Copyright ({ ‘unknown’, google });

This article is the third part of a series. I recommend you read the first article to understand the purpose of this series, and the second article to learn how to set up your development work environment. I should also mention that creating an API requires intermediate development skills, but this still doesn’t make it all that hard. And yeah, my writing style will be expository and with mild terminologies.

In our last article, we completed our development environment process, let’s build on that. In this article, we will be adding one major functionality, which will be creating the Cause CRUD. This means we will be able to create, retrieve, update, and delete causes which are the foundation of our application. So, let’s get started. We will create a new directory to house our codes. In this directory, we will create three additional directories for our models, controllers, and routes.

Heard of MVC? It’s an acronym for Model, View, Controller. You can read more about MVC here. We will build with the MVC architectural pattern which will basically breakdown and separates the different parts of our application arranging it all according to functionalities. The models will hold the database codes, the controller will hold the application logic that will process requests from the client and output response, and finally, the routes will handle all routing. Pun intended. Oh, we won’t be implementing view at this phase.

First, create and checkout to a new branch. Next, install the dependencies below, and create our directories. Do this using GitBash or any git integrated terminal.

// create and checkout to a new branch
git checkout -b ft-CRUD
// npm install the needed dependencies
npm install body-parser morgan mongoose --save
// create new directory
mkdir server
// change to server directory
cd server
// add three more directories
mkdir controllers models routes
npm installed needful dependencies and directories created

Configure Application Entry Point

The three dependencies we’ve just installed will do the following. Body-parser will extract the body portion of an incoming request and expose this on the req.body, as well as enable handling of HTTP requests such as POST, PUT, PATCH and DELETE. Morgan is the middleware that handles and logs request entries and responses to the console, and finally, mongoose is an object document mapper commonly used for MongoDB.

Next, we will make these dependencies available to our application by configuring them at the application’s entry point. Here’s the code for that.

// import dependencies
import express from 'express';
import bodyParser from 'body-parser';
import mongoose from 'mongoose';
import logger from 'morgan';
// set up dependencies
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false });
app.use(logger('dev'));
// set up mongoose
mongoose.connect('mongodb://localhost/projectsupport')
.then(()=> {
console.log('Database connected');
})
.catch((error)=> {
console.log('Error connecting to database');
});
// set up port
const port = 5035;
// set up route
app.get('/', (req, res) => {
res.status(200).json({
message: 'Welcome to Project Support',
});
});
app.listen(port, () => {
console.log(`Our server is running on port ${port}`);
});

The first twelve lines basically import and set up the dependencies making them available at our app.js file. The next seven lines after that basically use JavaScript promise to set up mongoose and handle any potential error. We’ve already defined the rest in our previous article. I should mention that line 14 shouldn’t be out there in the open, best practice requires we define this in a .env file, but we will work with this for now and return to it later.

You will need to have MongoDB installed in your computer to set up a database. If you already have MongoDB installed on your computer, please skip this, otherwise use this. A simpler guide can be found here. After installing MongoDB, you will need to start it up. This is pretty easy. I like to use the default command prompt for this. With that out of the way, let’s run our server.

// start mongoDB using CMD
// Locate mongodb and open directory which is most likely in C:/
cd mongodb
// change directories to bin
cd bin
// start mongod
mongod

Let’s start our server using our git terminal

npm run start:dev
Our server and database is set up

Creating Models

Next, we will create our model. Let’s start with a simple application that only requires we fill a title and a description when we want to create a cause. The cause we create will be stored in our MongoDB and made available to the client when it is requested for. Our first model will be cause.js and here’s the code for that. This will be typed using GitBash, or better the git integrated terminal in VSCode.

// create a cause.js in our models directory
touch server/models/cause.js
// open cause.js
start server/models/cause.js

Now, the actual code. This will be in cause.js

import mongoose from 'mongoose;mongoose.Promise = global.Promise;const causeSchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
});
export default mongoose.model('Cause', causeSchema);

This is pretty much straight forward. We imported mongoose and created a schema for storing in the database. Next, we will define the logic that actually creates cause. We will do this in the controller.

Creating Controller

Just like we created cause.js in models directory, you should create cause.js in the controllers directory, and type the code below in it.

import mongoose from 'mongoose';
import Cause from '../models/cause';
// create new cause
export function createCause(req, res) {
const cause = new Cause({
_id: mongoose.Types.ObjectId(),
title: req.body.title,
description: req.body.description,
});
return cause
.save()
.then((newCause) => {
return res.status(201).json({
success: true,
message: 'New cause created successfully',
Cause: newCause,
});
})
.catch((error) => {
res.status(500).json({
success: false,
message: 'Server error. Please try again.',
error: error.message,
});
});
}

So, there seems to be a lot of stuffs happening here, but don’t worry, it’s not hard. Line 1 and 2 basically imports mongoose and our cause model. Remember, we exported our cause model in the last line when creating cause model. The rest of the code basically creates a new cause. Here’s how it works. Line 6–10 creates the cause by first defining how the values will be gotten and then matching that to keys. Line 12–27 returns a promise and handles any potential error with the .catch method. If everything goes well, line 13–20 will run and store the created cause to the database, with line 16–18 sent as a response to the client. In the event of any error, line 21–27 will run, with line 23–25 sent as a response to the client.

Creating Routes

We’ve already done the difficult part, now we simply just define the route and expose it to our entry point. So let’s get started. Start with creating main.js in the routers directory just like we did for both models and controllers. Once this is done, type the code below.

import express from 'express';
import { createCause } from '../controllers/cause';
const router = express.Router();router.post('/causes', createCause);export default router;

We’ve done so much and it’s time to test what we’ve written. Oh, let’s add two lines to our entry point. This will export our routes and expose it on our entry point. From the code snippets below, line 4 imports our routes and line 9 sets it up with the /api prefix.

// call in the installed dependencies
...
import logger from 'morgan';
import mainRoutes from './server/routes/main';
...// set up route
app.use('/api/', mainRoutes);
...

Testing is actually fun with Postman. Launch Postman and let’s test to see if our application works.

Yay! Our home endpoint just worked
Our POST also worked. We just created a new cause
The response on the console is morgan at work

This is awesome. We just created a new cause and it's saved in MongoDB. It wasn’t so hard, was it? Now, we will need to write the logic to retrieve this cause, update the cause, and delete the cause. Our model is already defined so we won’t be doing this again, the logic will be contained in our controller, and we will finally add the endpoint in our router. So, let’s started.

In the controller directory, open up cause.js and type the code below after the createCause function. Oh, this should be before the export line.

// Get all causes
export function getAllCause({
Cause.find()
.select('_id title description')
.then((allCause) => {
return res.status(200).json({
success: true,
message: 'A list of all causes',
Cause: allCause,
});
})
.catch((err) => {
res.status(500).json({
success: false,
message: 'Server error. Please try again.',
error: err.message,
});
});
}

Next, we will import this in our route directory. Open up main.js in our route directory and type the code below.

import { createCause, getAllCause } from '../controllers/main';
...
router.post('/events', createCause);
router.get('/events', getAllCause);
export default router;
This is simply awesome. Now, we can retrieve all causes.

As seen in Postman, we can retrieve all causes and send the JSON response to the client. We still have a couple more endpoints to develop, so let’s get on it. In our controller directory, let's add the codes to retrieve a single cause. If you observe, you’d notice that the one unique thing about each cause is its ID. If you also notice, we didn’t define the ID ourselves, we allowed mongoose to take care of this. The benefit of mongoose generated ID includes a globally unique Id in all practical purpose. You can read more on mongoose ID here.

This unique Id makes it easy for us to access single causes. Basically what we will do look through the database and find by Id. Let’s write some codes for this. Do this after getAllCause.

// get single cause
export function getSingleCause(req, res) {
const id = req.params.causeId;
Cause.findById(id)
.then((singleCause) => {
res.status(200).json({
success: true,
message: `More on ${singleCause.title}`,
Cause: singleCause,
});
})
.catch((err) => {
res.status(500).json({
success: false,
message: 'This cause does not exist',
error: err.message,
});
});
}

Next, we add this endpoint to our main.js, head up there and add the code below:

...
import { createCause, getAllCause, getSingleCause } from '..controllers/cause';
...
router.get('/cause', getAllCause);
router.get('/cause/:causeId', getSingleCause);
...

Let’s test this using Postman… Simply copy out the id of a cause and add this to URL and send.

We can now retrieve details of a cause

Next, we will add both the update and delete endpoints. Again, we will add this to our controller. Let’s talk a bit about updating. The two HTTP methods available for this are PUT and PATCH and are sometimes used interchangeably in some tutorials. However, both really are not the same. PUT basically allows for a complete replacement of a document, while PATCH allows for a partial replacement of a document. In this article, we will be using PATCH.

Let’s get on with this already. To understand how the $set operator works for MongoDB, see this link.

// update cause
export function updateCause(req, res) {
const id = req.params.causeId;
const updateObject = req.body;
Cause.update({ _id:id }, { $set:updateObject })
.exec()
.then(() => {
res.status(200).json({
success: true,
message: 'Cause is updated',
updateCause: updateObject,
});
})
.catch((err) => {
res.status(500).json({
success: false,
message: 'Server error. Please try again.'
});
});
}

Next, we will head up to our main.js in the route directory and define our update endpoint.

...
import { createCause, getAllCause, getSingleCause, updateCause } from '../controllers/cause';
...
router.get('/causes/:causeId', getSingleCause);
router.patch('/causes/:causeId', updateCause);
...

With this done, next, we test using postman to check its working.

Yay! We just edited our cause

Yay! We are almost done. One last endpoint to work on, and that’s the delete endpoint. Let’s get on with that as well. Type the code below after the update cause.

// delete a cause
export function deleteCause(req, res) {
const id = req.params.causeId;
Cause.findByIdAndRemove(id)
.exec()
.then(()=> res.status(204).json({
success: true,
}))
.catch((err) => res.status(500).json({
success: false,
}));
}

As you should know by now, we will need to update this to our main.js in the route directory. Let’s do this right away. Open up main.js and type the code below.

...
import { createCause, getAllCause, getSingleCause, updateCause, deleteCause } from '../controllers/main';
...
router.patch('/cause/:causeId', updateCause);
router.delete('/cause/:causeId', deleteCause);
...

Let’s test this to be certain it actually works.

We have successfully deleted this cause.

Awesome. We have successfully deleted this cause. We can decide to output a response to the client, but for this to happen, we will need to replace the 204 status code to 200 and type a message. Now, let’s test to be sure this cause is deleted by trying to retrieve its content. Again, Postman.

Now, it fails to retrieve because we have deleted the cause

Yay! We have successfully written and tested to show it's working the codes for creating, retrieving, updating, and deleting a cause. With this, we have come to the end of this article. Oh one last thing, we will need to push our codes to our repository.

// to see a list of all changes
git status
// to add all changes to git
git add .
// to commit all changes to git
git commit -m 'completed the cause crud operations'
// to push changes to remote repository
git push origin ft-crud

If you need to see the complete codes and compare with what you have, here’s the link to the GitHub repository, ProjectSupport.

In our next article, we will be adding user signup and login to what we’ve already done, and yeah authentication will be done using JWT. We will also be protecting endpoints such as create, update and delete causes accessible only to users with an authenticated token.

To read about deploying to mLab and Heroku, click here.

If you liked this article or have an issue with implementing the codes on your end, do let me know in the comment section and I will be waiting. And yeah, I will like to know that your codes worked as expected too.

--

--