Introduction to creating REST APIs using Node.js

This post will explain how to implement REST APIs using NodeJS, to execute the basic CRUD operations.

We will also be using ExpressJS along with MongoDB, to create a catalog/list of superheroes.

Let us first use npm init to initialize a package.json file in our project directory.

npm init

You can use the default values for the prompts, or customize them as you like.

Creating the basic Express Server and configuring nodemon

We will be working with Express along with Node to create our APIs. Install it using:

npm install --save express@4.17.1

ExpressJS allows us to enhance our development experience by abstracting a lot of low level coding and configurations which we would otherwise have to do, while creating our APIs.

Let us first create a single file called app.js in our project directory, and configure our Express server. As mentioned, we will be creating a simple application which allows us to create a catalog of superheroes.

//app.jsconst express = require(‘express’);const app = express(); //obtain an express application instanceconsole.log(“Started Heroes”);app.listen(8081); //the express application will live here

This gives us a server which will be hosted on localhost:8081

We will now install a package called nodemon and then configure our package.json file to have a start script which we can directly use to start our server. Nodemon will allow us to skip restarting the server again and again after every change in our files.

npm install --save-dev nodemon@1.18.4

Add the following to your scripts in package.json:

“scripts”: {
“test”: “echo \”Error: no test specified\” && exit 1",
“start”: “nodemon app.js”
}

With this, type npm start from your project’s directory in the terminal and you should see the following result:

[nodemon] 1.18.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node app.js`
Started Heroes

Congratulations! You have successfully set up your server, we can now move on to listening to requests on this server. However, let us first have a glimpse at Express middlewares, using which we will handle our incoming requests.

Handling requests in express using middlewares

All incoming requests to the server will be funneled through our middlewares, created using the app.use( ) method provided by express on the app instance we obtained above.

app.use( ) receives a callback, with three parameters for the request(req), response(res) and next (a function provided by Express, to move on to the next middleware).

A typical middleware which we will be using, looks like this in its basic form:

app.use((req, res, next) => {
//req gives us access to the request
//next is used to move to the next middleware
//res gives us access to the response
});

These middlewares are used to capture a request (req), use it for the desired effect and then send a response (res). You can say that middlewares live in between a request being received by a server, and until a response is sent back to the client, in this case.

In case of multiple middlewares, we move to the next middleware by using the next argument given to us in the callback by Express. Here is an example, try adding these after the console.log statement in the app.js file you created earlier:

app.use((req, res, next) => {
console.log(“Middleware 1”);
next(); //move to the next middleware
});
app.use((req, res, next) => {
console.log(“Middleware 2”);
next(); //move to the next middleware
});

Now in your browser, visit localhost:8081. The browser will be unable to render any result, since we are not sending any actual response yet. A request however, is made to your server on hitting this path.

Check your terminal logs. The request should be able to reach your middlewares, and produce the following result:

It is necessary to call next unless we are sending a response, otherwise the request will be stuck inside the middleware.

The req and res arguments in the above code give us access to the request and response objects respectively. We can therefore use the methods available on these objects as per our requirement, and make modifications to the request and response. Here is an example, where we send back a simple HTML page as response. Add it after the middlewares we created earlier in the app.js file, and again visit localhost:8081:

app.use((req, res, next) => {
console.log(“I don’t have to call next()as I send the response”);
res.send("<h1>Hey There!</h1>");
});

More on middlewares here.

The first hurdle: Handling CORS issues

An advantage of using REST APIs is that it decouples the back end server logic from the client. This can allow a single API to be used on multiple platforms which require the same data, like a mobile app and web app where both require the weather data for a location. A single API can be created to serve both clients’ requests.

This decoupling however, also means that the client’s code may be running on a different domain than the server, as is often the case with modern day single page applications. To ensure that no issues arise with resource sharing, we will need to enable CORS (Cross Origin Resource Sharing) for every incoming request to our application first, using a middleware. This middleware will run for every request that is made to the server.

Although you most probably would have not encountered CORS issues the way this tutorial goes on, you would eventually have to encounter it when implementing what you learn from this blog in an actual project.

Remove the previous middlewares from app.js, which were only used to establish a basic understanding.

Add the following middleware instead, to enable CORS, and app.js should end up looking like this:

// app.jsconst express = require(‘express’);
const app = express();
console.log(“Started Heroes”);//middleware to enable CORS
app.use((req, res, next) => {

//here we are manipulating the response object configuration
//not yet sending back the response, only setting headers
res.setHeader(
‘Access-Control-Allow-Methods’,
‘GET, POST, PATCH, PUT, DELETE’
);

res.setHeader(
‘Access-Control-Allow-Origin’,
‘*’
);
res.setHeader(
‘Access-Control-Allow-Headers’,
‘Content-type, Authorization’
);
next();
});
app.listen(8081);

Handling a GET request using Express Router

Great! We have wired up the basic skeleton, and can proceed with configuring the routes for our APIs.

In our app.js file, we could straightaway configure a GET route as follows after the CORS middleware we just added, say for the route ‘/getHeroes’ :

...app.use(‘/getHeroes’, (req, res, next) => {
// a controller passed as callback for the route
res.json([
{
realName: “Tony Stark”,
heroName: “Iron Man”
},
{
realName: “Bruce Wayne”,
heroName: “Batman”
}
]);
//send JSON response
});...

However our code will get too clustered and hard to read when things get a bit more complex.

We will therefore keep the routes and their controllers in separate files.

Create two new folders, routes and controllers, in your project directory. It should look something like this:

Create a new file called heroes.js in your routes folder:

In the heroes.js file you just created, proceed to write the code for your route as follows:

// routes -> heroes.jsconst express = require(‘express’);const router = express.Router(); //obtain an instance of a routerrouter.get(‘/getHeroes’, (req, res, next) => {
//This is the controller for /getHeroes, passed as a callback
//We will move it into a separate file later
res.json([
{
realName: “Tony Stark”,
heroName: “Iron Man”
},
{
realName: “Bruce Wayne”,
heroName: “Batman”
}
]);
//send json response
});
module.exports = router;

Notice the similarity it has with the previous middleware we wrote.

Now we can include the exported router in our app.js file, passing it to app.use() to be used as a middleware.

//app.jsconst express = require(‘express’);
const heroRouter = require(‘./routes/heroes’);
const app = express();console.log(“Started Heroes”);app.use((req, res, next) => {
res.setHeader(
‘Access-Control-Allow-Origin’,
‘*’
);
res.setHeader(
‘Access-Control-Allow-Methods’,
‘GET, POST, PATCH, PUT, DELETE’
);
res.setHeader(
‘Access-Control-Allow-Headers’,
‘Content-type, Authorization’
);
next();
});
app.use(heroRouter);app.listen(8081);

If you have successfully managed to set up the router, the response in your browser on hitting the path ‘/getHeroes’, should look something like this:

JSON response for the GET route /getHeroes

Cool, you have just learned how to create a basic GET request. Before we move on to creating a POST request, we will need to do something about parsing incoming requests first.

Handling POST requests

We will need to install a package called body-parser, to make our incoming requests easy to handle. Without this package, we would have to deal with the raw request. For example, raw requests are received by your server in chunks of data. Without body-parser, we would have to manually parse it by creating buffers for these chunks of data, concatenate those buffers and then process the request body, which is received as a string and will need to be translated into something meaningful like JSON.

This package parses an incoming request, very conveniently making the body of an incoming request accessible via the req parameter we pass to our middlewares, on the req.body property. We will see this in action soon.

npm install --save body-parser@1.19.0

body-parser is a valid middleware, and we can add it to our app.js file, before our other middlewares:

const express = require(‘express’);const bodyParser = require(‘body-parser’);const heroRouter = require(‘./routes/heroes’);
const app = express();
console.log(“Started Heroes”);app.use(bodyParser.json());...

bodyParser.json( ) will parse the json data from the body of any incoming request, and make it available on the request object req, via req.body

Let us now create a POST route, to handle the creation of a hero. Go to your routes/heroes.js file, and add the following route:

// routes -> heroes.js...router.post(‘/createHero’, (req, res, next) => {
const realName = req.body.realName;
const heroName = req.body.heroName;
res.json({
message: “Created hero successfully!”,
realName: realName,
heroName: heroName
});
});
module.exports = router;

For now, we are not saving the data for the hero anywhere, and instead returning the data as it is in the response. We expect the client to pass a realName and heroName in the request body. Using postman to make this request, we get the following result:

Response for the POST request to /createHero

You just created a basic POST route. We will further add status codes to our responses, which is pretty straightforward. It is illustrated below for the POST route we just created:

router.post(‘/createHero’, (req, res, next) => {
const realName = req.body.realName;
const heroName = req.body.heroName;
res.status(201)
.json({
message: “Created hero successfully!”,
realName: realName,
heroName: heroName
});
});

Try adding a status of 200 for the GET route we created earlier in routes/heroes.js

Modularizing our controllers

Currently our routes in the routes/heroes.js file look like this:

// routes -> heroes.jsrouter.get('/getHeroes', (req, res, next) => {
res.status(200).json([
{
realName: "Tony Stark",
heroName: "Iron Man"
},
{
realName: "Bruce Wayne",
heroName: "Batman"
}
]);
});
router.post('/createHero', (req, res, next) => {
const realName = req.body.realName;
const heroName = req.body.heroName;
res.status(201).json({
message: "Created hero successfully!",
realName: realName,
heroName: heroName
});
});

We will modularize our controllers so that the routes instead look like this:

// routes -> heroes.jsconst heroesController = require('../controllers/heroes');router.get(‘/getHeroes’, heroesController.getHeroes);
router.post(‘/createHero’, heroesController.createHero);

To do this, go to your controllers folder and then create a heroes.js file there:

heroes.js inside the controllers folder

We will now have the callbacks we were using earlier, exported from this file:

// controllers -> heroes.jsexports.getHeroes = (req, res, next) => {
res.status(200).json([
{
realName: "Tony Stark",
heroName: "Iron Man"
},
{
realName: "Bruce Wayne",
heroName: "Batman"
}
]);
};
exports.createHero = (req, res, next) => {
const realName = req.body.realName;
const heroName = req.body.heroName;
res.status(201).json({
message: "Created hero successfully!",
realName: realName,
heroName: heroName
});
};

We can now safely change the code in our routes/heroes.js file to look like we had intended. This is how it should look now:

// routes -> heroes.jsconst express = require('express');
const router = express.Router();
const heroesController = require('../controllers/heroes');router.get(‘/getHeroes’, heroesController.getHeroes);
router.post(‘/createHero’, heroesController.createHero);
module.exports = router;

Connecting our app with MongoDB

It is just plain boring to return the created object as it is, and not practical for a complete REST tutorial. We will further need the data saved somewhere for our PUT and DELETE requests to work, for editing or deleting a hero.

Setting up a Database

To learn how to install and start using MongoDB for your machine, refer here. You may also use MongoDB Atlas for proceeding ahead.

Start your mongoDB server and proceed to acquire the path pointing to it. In case you are running it on your local machine, you will find the path to your DB listed in the terminal:

Path to your MongoDB server

We will also install MongoDB Compass on our system, which gives us a convenient GUI to work with our DBs (and will also help to keep this blog focused on Node and not mongoDB). You can get it here.

Open Compass, and paste the path to your mongoDB server and click on connect to see what it looks like so far:

With this, we may start using our database by connecting it to our node application.

To begin, we will add mongoDB and mongoose to our project:

npm install --save mongodb mongoose

We will now import mongoose in our app.js file:

// app.jsconst express = require('express');
const bodyParser = require('body-parser');
const mongoose = require(‘mongoose’);
...

We will now connect to our database using mongoose. To ensure that we only start our server when the connection is established, we will transform (still in app.js):

// app.js...app.listen(8081);...

To instead become:

mongoose.connect('mongodb://127.0.0.1:27017/heroes')
.then(result => {
console.log('connected');
app.listen(8081);
}).catch(err => {
console.log(err);
});

We pass our database connection string to mongoose.connect() as an argument, with /heroes appended to the path as shown above. MongoDB will create a ‘heroes’ database automatically for you if you attempt to access it with your server code using this connection, as we will do in a while.

This method returns a promise. We chain a then call to this promise, and listen to the server once the connection has been established.

Our next step will be to create a mongoose model for the hero which we will use to interact with the database.

First, create another directory called models where we will place our model file. Inside this directory, create a file called hero.js, this file will have the schema for our hero model:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const heroSchema = new Schema({
heroName: {
type: String,
required: true
},
realName: {
type: String,
required: true
}
});
module.exports = mongoose.model('Hero', heroSchema, 'heroes');

We will now make changes to our POST route controller, so that it uses this model to save a hero’s data into our heroes database. Open your controllers/heroes.js file and make the following changes to the createHero controller:

// controllers-> heroes.jsconst Hero = require('../models/hero'); //import the Hero model...exports.createHero = (req, res, next) => {    const realName = req.body.realName;
const heroName = req.body.heroName;
const hero = new Hero({
realName: realName,
heroName: heroName
});
hero.save()
.then(result => {
res.status(201).json({
message: "Created hero successfully!",
result
});
}).catch(err => {
console.log(err);
next();
});
};

The hero.save() method is provided by mongoose. This will save a hero object to the heroes database, in a collection which is also called ‘heroes’ (refer to the model/hero.js file, where we exported the module. We specified the collection name we want in the DB as the third argument, to the mongoose.model() method).

The result in the .then call contains the details of the object which was just saved.

We will again make a POST request using Postman, to create a hero on the path /createHero, passing values for the required realName and heroName properties as shown:

Open MongoDB Compass using your database connection string, to check these changes. You will see that a heroes database and a heroes collection were created for you. In the heroes collection, you will find your entry:

Great! We can now use this database to demonstrate our other requests.

Editing and deleting heroes

To edit a hero, we will need to have a reference for that particular hero in our collection. We can use any kind of unique identifiers, like in our case, even hero names can be used since every hero has a different name for their hero avatar.

However, it is always a good practice to have some kind of ID available for this. For our simple application, we will just be using the ObjectId provided by mongoDB.

First, let us create a route to capture a request to edit the hero. This request will be a PUT request, as we will be overwriting an existing resource.

In your routes/heroes.js file, add the following route:

router.put('/editHero/:heroId', heroesController.editHero);

Where :heroId will be used as a request parameter to identify the hero to be edited in our controller. This will become clear in our controller code ahead.

Now, we will create a controller for this route. Go to your controllers/heroes.js file and add the following controller:

// controllers -> heroes.js...exports.editHero = (req, res, next) => {

//body-parser package gives us access to request params
const heroId = req.params.heroId;

const realName = req.body.realName;
const heroName = req.body.heroName;
Hero.findById(heroId)
.then(hero => {
hero.realName = realName;
hero.heroName = heroName;
return hero.save();
})
.then(result => {
console.log("Edited successfully!");
res.status(200).json({
message: "Edited successfully",
result
});
})
.catch(err => {
console.log(err);
next();
});
};

Using Compass, copy the object ID of the existing hero in your heroes collection:

Copying ObjectId using Compass

Now using Postman, we will make a PUT request to edit our hero (editing Peter Parker’s hero name to Venom) on the path we specified earlier:

We have edited Peter Parker to now have the hero name “Venom”

And there you have it, you have implemented your API for editing a hero.

Deletion of a hero from the collection follows a similar approach.

Add a route to handle delete requests, in routes/heroes.js:

...router.delete('/deleteHero/:heroId', heroesController.deleteHero);...

Add a controller in your controllers/heroes.js file:

exports.deleteHero = (req, res, next) => {    const heroId = req.params.heroId;    Hero.findByIdAndRemove(heroId)
.then(result => {
console.log("Deleted Hero");
res.status(200).json({
message: "Deleted Hero",
result
});
})
.catch(err => {
console.log(err);
next();
})
};

Now execute the request using Postman, for the route /deleteHero/:heroId as shown:

Deletion request for hero, passing the hero’s ObjectId as a request param

You can use Compass to check that the hero has been deleted from your collection.

As an exercise, try updating the controller for the GET route /createHeroes which we made before, to now fetch the Hero list from out database instead of the static response which we were sending earlier.

Also try to implement a new route, to fetch a single hero using the hero’s ObjectId.

I hope that by now, you have become capable of successfully attempting the above exercises.

Parting Note

One of the fundamental principles of REST APIs, is that they are stateless. Client requests should contain all the information necessary for identification, and this is generally implemented using JWTs (JSON Web Tokens).

In a real world app, they are sent along with every request, to identify the client and check whether they have the permissions necessary to access a resource. Generating JWTs, would require implementing user sign up and authentication to generate unique tokens for users. Error handling in a Node/Express application and request validations are other things which would make our application more robust.

I will be covering these along with other such topics in future posts, hopefully. Until then, happy coding!

Building things, breaking things, learning things

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store