#100DaysOfCode Day 29: REST API using Node.js
Hello, World.
Today, we will be going over one of the most basic concepts of back-end web development: REST APIs. I know, I know. This is probably the fourth article I’ve written about REST APIs. But, I want to make sure that I truly master the ins-and-outs of RESTful APIs.
You can find the tutorial I followed on Bezkoder’s website.
Requirements
The requirements for today’s small project is simple:
- Your code editor. I use Vim.
- Node.js: our server-scripting environment.
- MongoDB: the database we will be using. You will not be needing Compass.
- Postman: a really good software for API testing and to send HTTP requests.
You will also need to install the following Node dependencies:
- Express: one of Node.js’ most popular web frameworks. It supports routing, middleware, view system, etc.
- Mongoose: a promise-based Node.js Object Data Manager (ODM) for MongoDB. It provides a straight-forward, schema-based solution to model our data along with built-in type casting, validation, query building, and business logic hooks.
- Body-parser: helps to parse the request and create the
req.body
object - Cors: a Node.js package for providing a Connect/Express middleware that can be used to enable CORS (Cross-Origin Resource Sharing) with various options.
- Nodemon: to automatically rerun the Node application when any changes are made. It is really useful for a more efficient development process.
These dependencies can be installed using the following command:
npm install express mongoose body-parser cors nodemon
Overview
We will build a REST API that can run CRUD methods (Create, Read, Update, Delete) on a database. We will refer to the information inside the database as Tutorials.
First, we will create an Express web server. Next, we will configure our MongoDB by creating a model with Mongoose and write the controllers to access the database. Finally, we will define the routes to handle the CRUD operations based on our predefined controllers.
The following list is an overview of what our REST API will include:
- GET api/tutorials: retrieve all Tutorials inside the database
- GET api/tutorials/:id: retrieve a Tutorial based on its unique ID
- POST api/tutorials: add a new tutorial
- PUT api/tutorials/:id: update a Tutorial based on its unique ID
- DELETE api/tutorials/:id: delete a Tutorial based on its unique ID
- DELELTE api/tutorials: delete all Tutorials
- GET api/tutorials/published: find all published Tutorials.
We can then test our database by using Postman.
Create an Express Web Server
We will start by creating our project’s directory. In my case, I created a directory named node-blog
using the following commands:
mkdir node-blog
cd node-blog
After making your project’s directory, initialize your Node Package Manager using npm init
. This will create a package.json
file that will have all of the important information on the project. Your package.json
file should more-or-less look like the following:
{
"name": "node-api",
"version": "1.0.0",
"description": "Create a REST API using Node.js that can run CRUD methods to a database."
"main": "server.js"
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/RussellAjax/100DaysOfCode-Day29-node-api.git"
},
"keywords": [
"REST",
"API"
]
"author": "Russell",
"license": "MIT",
"bugs": {
"url": "https://github.com/RussellAjax/100DaysOfCode-Day29-node-api/issues"
},
"homepage": "https://github.com/RussellAjax/100DaysOfCode-Day29-node-js#readme",
"dependencies": {
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"express": "^4.17.1",
"mongoose": "^5.9.19",
"nodemon": "^2.0.4"
}
}
In your root directory, create a new server.js
file with the following code:
In the first block of the code, I imported the required dependencies for this file. Here, I imported express
, body-parser
, and cors
.
Next, I created an Express app by running express();
inside our app
variable. I also called body-parser
and cors
middleware. Our API is then accessible by making the default \
route, which will print out a JSON message.
Finally, I created an app.listen()
to set the port in which the program can be executed.
Configure MongoDB Database
Before continuing, create an app
directory within your root folder. Our app
directory will hold all of the application’s assets such as database configuration and models.
After creating the foundation of the Express application, let us configure the database that will hold all of the user-inputted data. First, create a new directory named config
(remember, this file should be inside the new app
directory we have just created), which will have the file db.config.js
. This file is used to initialized and configure our MongoDB database. The file should have the following code:
module.exports = {
url: "mongodb://localhost:27017/node-db"
};
Next, we can create our Mongoose database model. Inside the app
directory, make a new directory named models
. Inside models
, create a file named tutorial.model.js
. This will be our data model.
The tutorial.model.js
file should have the following code:
After creating your model files, I am going to initialize the MongoDB database by creating an index.js
file within app/models
. To initialize your database, index.js
should have the following code:
Now, we will have to call the initialized database to our main server.js
file that is located outside of the app
directory. After calling app.use()
on the project’s dependencies, add the following code to connect our database:
const db = require("./app/models");
db.mongoose
.connect(db.url, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => {
console.log("Connected to the database!");
})
.catch(err => {
console.log("Cannot connect to the database!", err);
process.exit();
});
Just like that, we are done with creating the Mongoose model and connecting the database to our application! Our next step is to create the controllers that can work on the database.
Creating the Controllers
Create a new directory inside app
and call it contollers
. Next, create a new file named tutorial.controller.js
. This file will contain all of our CRUD methods with functions such as create
, findAll
, findOne
, update
, delete
, deleteAll
, and findAllPulished
.
Start by importing the data’s model:
const db = require("../models");
const Tutorial = db.tutorials;
Now, let’s go step-by-step in creating our controllers.
create
The create
controller will be used to create a new entry to our database. The code is as follows:
exports.create = (req, res) => {
//Validate request, field cannot be empty
if(!req.body.title || !req.body.description){
res.status(400).send({message: "Content cannot be empty!"});
return;
}
//Create entry for user's input
const tutorial = new Tutorial({
title: req.body.title,
description: req.body.description,
published: req.body.published ? req.body.published: false
}); //Save the entry to the database
tutorial
.save(tutorial)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({message: "Cannot save data."});
});
};
In the first block of the code, I wrote a simple conditional statement to check whether the user filled all of the required fields. Using !req.body.title
and !req.body.description
meant that there is no entry inside the request’s title
section or the description
. If there is an empty field, then the response from the application would be to give a Content cannot be empty!
message.
Next, I created a new constant named tutorial
. This constant will hold the information provided by the request. By using new Tutorial
, I can form tutorial
according to the pre-defined Mongoose model and assign the fields such as title
, description
, and published
based on the request’s body.
Lastly, I saved the tutorial
constant to the database using .save(tutorial)
. If saving the data goes smoothly, then the response from the application would be to print out the data
, which is the information inside the tutorial
field. However, if there is an error, I used .catch()
to handle the error by sending a 500 status code and log an error message.
findAll
The next controller is findAll
, which will log all of the saved entries inside the database. This is command will retrieve all objects without any condition. In this code, we will use req.query.title
to get the query string from the request. The code should look like the following:
exports.findAll = (req, res) => {
const title = req.query.title;
var condition = title ? {title: {$regex: new RegExp(title), $options: "i" } } : {};
Tutorial.find(condition)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({message: "An error occured while retrieving entries"});
});
};
findOne
Similar to the findAll
controller, findOne
will be used to find a single Tutorial entry with a specific id. The code is as the following:
exports.findOne = (req, res) => {
const id = req.params.id; Tutorial.findById(id)
.then(data => {
if(!data){
res.status(404).send({message: `No tutorial found with id=${id}`});
}else{
res.send(data);
})
.catch(err => {
res.status(500).send({message: "Error retrieving Tutorial"});
});
};
In the first line of the controller, we created a new constant called id
. This constant will hold the id of data based on the request sent by the user. Next, we will go over the Tutorial
database and track its id using the findById()
function. If the search goes without error but no entry can be found, then the application will send a 404 response. However, if there is an entry, then the application will print out the requested data.
update
The next controller is to update an entry that is already inside the database. To do this, we can use the findByIdAndUpdate()
function. The code for the update
controller is as follows:
exports.update = (req, res) => {
if(!req.body){
return res.status(400).send({message: "Update cannot be empty!"});
} const id = req.params.id; Tutorial.findByIdAndUpdate(id, req.body, { useFindAndModify: false })
.then(data => {
if(!data){
res.status(404).send({message: "Cannot update entry!"});
}else{
res.send({message: "Update successful!"});
}
})
.catch(err => {
res.status(500).send({message: "Error updating!"});
});
}
In the first block of code, I wrote a condition statement that ensures the user does not update the entry with an empty blank. If the user does update data with an empty field, then the application will not save it and send an error message to the user.
Afterward, I called an id
constant that will be used to find and update a specific data entry. We will be using findByIdAndUpdate()
to update the contents of the data.
delete
This controller will delete specific data based on its id
. We will utilize the findByIdAndRemove()
function to delete the data. The code for delete
should be as follows:
exports.delete = (req, res) => {
const id = req.params.id; Tutorial.findByIdAndRemove(id)
.then(data => {
if(!data){
res.status(400).send({message: "Could not find data to delete!"});
}else{
res.send({message: "Data deleted successfully!"});
}
})
.catch(err => {
res.status(500).send({message: "Failed to delete data."});
});
};
deleteAll
Unlike delete
, the deleteAll
controller will delete all of the data inside the database. To do this, we will be using the deleteMany()
function. The code to delete all of the data should be as follows:
exports.deleteAll = (req, res) => {
Tutorial.deleteMany({})
.then(data => {
res.send({message: "All of the Tutorials were successfully deleted."});
})
.catch(err => {
res.status(500).send({message: "An error occured".});
});
};
findAllPublished
The last controller is the findAllPublished
controller. The will use the find()
function with published: true
as the argument. The code is as follows:
exports.findAllPulished = (req, res) => {
Tutorial.find({ published: true })
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({message: "An error occured."});
});
};
Overall, your tutorial.controller.js
file should look like the following:
Define Routes to Handle CRUD Operations
After creating our controllers, we will need to define the routes in which the user can send their HTTP requests to perform the CRUD operations. To do this, create a directory within app
called routes
. The name of the file should be tutorial.routes.js
and it should have the following:
The code above basically assigns a particular route to a specific controller based on the request sent by the user. The last code sets the default route to call these methods. For example, to retrieve all published Tutorials means that the user will have to call a GET method to localhost:3000/api/tutorials/published
.
Finally, we should include these routes back to our main server.js
file with the following code:
require('./app/routes/tutorial.routes')(app);
Input the code above just before app.listen()
, and you are finished! You can test your APIs using Postman.
Conclusion
Today’s work is really helpful in understanding more about REST APIs to call CRUD methods. In short, making REST APIs using Node.js, Express, and MongoDB requires the following:
- Start your Express app
- Configure your MongoDB models
- Create the controllers
- Define routes to the controllers
Thank you for reading through this article!