Building A REST API With MongoDB, Mongoose, And Node.js

Building A REST API With MongoDB, Mongoose, And Node.js

In this tutorial, we build A REST API With MongoDB, Mongoose, And Node.js .... Mongoose is Nodejs package for modeling Mongodb.

In this tutorial, we build A REST API With MongoDB, Mongoose, And Node.js .... Mongoose is Nodejs package for modeling Mongodb.

About a week or so ago I had written a tutorial titled, Getting Started with MongoDB as a Docker Container Deployment, which focused on the deployment of MongoDB. In that tutorial we saw how to interact with the MongoDB instance using the shell client, but what if we wanted to actually develop a web application with MongoDB as our NoSQL database?

In this tutorial we’re going to see how to develop a REST API with create, retrieve, update, and delete (CRUD) endpoints using Node.js and the very popular Mongoose object document modeler (ODM) to interact with MongoDB.

Before we get too invested in this tutorial, I wanted to point out that the focus of this tutorial won’t be around installing, configuring, or deploying MongoDB instances. I recommend you take a look at my previous tutorial if you need help with that. The assumption is that your MongoDB database is ready to go.

Configuring Node.js with Express Framework

To start things off, let’s go ahead and create a fresh Node.js project with the appropriate dependencies. From the command line, execute the following:

npm init -y
npm install express body-parser mongoose --save


The above commands will create a new package.json file and install Express.js, Mongoose, and a package that will allow us to pass JSON data around in our requests.

For simplicity, we’re going to keep all of our code within a single JavaScript file. Create an app.js file at the root of your project and include the following boilerplate code:

const Express = require("express");
const Mongoose = require("mongoose");
const BodyParser = require("body-parser");

var app = Express();

app.use(BodyParser.json());
app.use(BodyParser.urlencoded({ extended: true }));

app.post("/person", async (request, response) => {});
app.get("/people", async (request, response) => {});
app.get("/person/:id", async (request, response) => {});
app.put("/person/:id", async (request, response) => {});
app.delete("/person/:id", async (request, response) => {});

app.listen(3000, () => {
    console.log("Listening at :3000...");
});


In the above code we are doing a few things. First we are importing each of the dependencies that we had previously downloaded when creating our project. Next we are initializing Express Framework and configuring the body-parser package so we can receive JSON data in our payloads.

The API we develop will be create, retrieve, update, and delete (CRUD) oriented hence the five endpoint functions that are prepared. We’ll be adding the MongoDB and Mongoose logic to each of these endpoint functions.

Finally, we are listening for requests to our application on port 3000.

Interacting with MongoDB using the Mongoose ODM

With the foundation of our REST API in place, we can focus on the database logic. Within the project’s app.js file include the following near where you initialized Express Framework:

Mongoose.connect("mongodb://localhost/thepolyglotdeveloper");


The above line will connect to our MongoDB instance running on localhost and it will either use or create a thepolyglotdeveloper database. If you followed the Docker example, the database will have already been created. If you’re not using localhost, change it up as necessary.

After the connection information is in place, we can define our document models. This particular example will only have a single document model that looks like the following:

const PersonModel = Mongoose.model("person", {
    firstname: String,
    lastname: String
});


Our model is person which will create a people collection within our database. Each document in our collection will have the firstname and lastname properties. The model can be significantly more complex than what we have here.

So let’s take a look at each of our endpoints, starting with the creation of data:

app.post("/person", async (request, response) => {
    try {
        var person = new PersonModel(request.body);
        var result = await person.save();
        response.send(result);
    } catch (error) {
        response.status(500).send(error);
    }
});


When the client makes a POST request to our endpoint, we can use our PersonModel and the JSON payload provided to save into the database. Only the most basic of data validation is happening on our JSON payload and rather than using promises directly, we are using async and await, which in my opinion is a little cleaner. If the save is successful, we return the data back to the client facing application.

Now that we have data in our database, we can try to retrieve it:

app.get("/people", async (request, response) => {
    try {
        var result = await PersonModel.find().exec();
        response.send(result);
    } catch (error) {
        response.status(500).send(error);
    }
});


There are two different retrievals that can happen. We can retrieve everything, or we can retrieve something in particular. The first retrieval that we are looking at is the everything scenario. Using a find with no properties will get all data for a given collection, which in our scenario is the people collection. That data is then returned to the client facing application.

On the other side of things, we can try to retrieve a single document based on its stored id value:

app.get("/person/:id", async (request, response) => {
    try {
        var person = await PersonModel.findById(request.params.id).exec();
        response.send(person);
    } catch (error) {
        response.status(500).send(error);
    }
});


Given an id value defined by the client facing application, we can call the findById function rather than the find function. This function will find a single document based on the associated id. When we have this data we will return it back to the user.

Not bad so far, right?

Let’s finish this simple API with an update and a delete endpoint. Starting with an update, we can do the following:

app.put("/person/:id", async (request, response) => {
    try {
        var person = await PersonModel.findById(request.params.id).exec();
        person.set(request.body);
        var result = await person.save();
        response.send(result);
    } catch (error) {
        response.status(500).send(error);
    }
});


When the client provides an id value, we can first find the document by the id. Once we’ve found the document, we can set the properties that were provided in the request payload. Again, basic validation is performed. For example, if a property is provided that doesn’t appear in our model, it will be stripped out. In our model, none of the properties are required. This means whatever data appears in the payload, as long as it is valid, it will override what already exists. We can save any changes we made back to the database and return the data back to the client.

Our final endpoint for this example is the delete endpoint:

app.delete("/person/:id", async (request, response) => {
    try {
        var result = await PersonModel.deleteOne({ _id: request.params.id }).exec();
        response.send(result);
    } catch (error) {
        response.status(500).send(error);
    }
});


Again, we are expecting an id to be provided from the client facing application. When we have an id, we can use it in the deleteOne function and the document will be removed from the database.

If you run your application, you can play around with it using a tool like Postman or similar.

Conclusion

You just saw how to build a simple RESTful API using popular technologies such as Node.js, JavaScript, and MongoDB as the NoSQL database. If you’ve been following the blog, you might remember I did something similar in a tutorial titled, Developing a RESTful API with Node.js and MongoDB Atlas. With Atlas, we built a REST API, but it was with a cloud deployment of MongoDB.

If you’re interested in learning more about RESTful API development, I encourage you to check out my eBook and video course titled, Web Services for the JavaScript Developer, as it goes into significantly more depth.

A video version of this article can be found below.

Using Hapi.js, Mongoose, And MongoDB To Build A REST API

Using Hapi.js, Mongoose, And MongoDB To Build A REST API

In this tutorial we’re going to develop a simple RESTful API using Hapi.js, Joi and Mongoose as the backend framework, and MongoDB as the NoSQL database. Rather than just using Hapi.js as a drop in framework replacement, I wanted to improve upon what we had previously seen, by simplifying functions and validating client provided data.

In this tutorial we’re going to develop a simple RESTful API using Hapi.js, Joi and Mongoose as the backend framework, and MongoDB as the NoSQL database. Rather than just using Hapi.js as a drop in framework replacement, I wanted to improve upon what we had previously seen, by simplifying functions and validating client provided data.

To continue on my trend of MongoDB with Node.js material, I thought it would be a good idea to use one of my favorite Node.js frameworks. Previously I had written about using Express.js with Mongoose, but this time I wanted to evaluate the same tasks using Hapi.js.

If you haven’t seen my previous tutorial, don’t worry because it is not a requirement. However, the previous tutorial is a valuable read if you’re evaluating Node.js frameworks. What is required is having a MongoDB instance available to you. If you’re unfamiliar with deploying MongoDB, you might want to check out my tutorial titled, Getting Started with MongoDB as a Docker Container Deployment

Creating a Hapi.js Project with MongoDB and Data Validation Support

With MongoDB available to us, we can create a fresh Hapi.js project with all the appropriate dependencies. Create a new project directory and execute the following commands:

npm init -y
npm install hapi joi mongoose --save


The above commands will create a new package.json file and install the Hapi.js framework, the Joi validation framework, and the Mongoose object document modeler (ODM).

We’re going to add all of our application code into a single project file. Create an app.js file and include the following boilerplate JavaScript code:

const Hapi = require("hapi");
const Mongoose = require("mongoose");
const Joi = require("joi");

const server = new Hapi.Server({ "host": "localhost", "port": 3000 });

server.route({
    method: "POST",
    path: "/person",
    options: {
        validate: {}
    },
    handler: async (request, h) => {}
});

server.route({
    method: "GET",
    path: "/people",
    handler: async (request, h) => {}
});

server.route({
    method: "GET",
    path: "/person/{id}",
    handler: async (request, h) => {}
});

server.route({
    method: "PUT",
    path: "/person/{id}",
    options: {
        validate: {}
    },
    handler: async (request, h) => {}
});

server.route({
    method: "DELETE",
    path: "/person/{id}",
    handler: async (request, h) => {}
});

server.start();


We’ve added quite a bit of code to our app.js file, but it isn’t really anything beyond the Hapi.js getting started material found on the framework’s website. Essentially we’ve imported the dependencies that we downloaded, defined our servers settings, defined the routes which are also referred to as endpoints, and started the server.

You’ll notice that not all of our routes are the same. We’re developing a create, retrieve, update, and delete (CRUD) based REST API with validation on some of our endpoints. In particular we’ll be adding validation logic to the endpoints that save data to the database, not retrieve or remove.

With our boilerplate code in place, lets take a look at configuring MongoDB and adding our endpoint logic.

Interacting with the Database using the Mongoose ODM

Remember, I’m assuming you already have access to an instance of MongoDB. At the top of your app.js file, after you defined your server configuration, we need to connect to MongoDB. Inculde the following line to establish a connection:

Mongoose.connect("mongodb://localhost/thepolyglotdeveloper");


You’ll need to swap out my connection string information with your connection string information. When working with Mongoose, we need to have a model defined for each of our collections. Since this is a simple example, we’ll only have one model and it looks like the following:

const PersonModel = Mongoose.model("person", {
    firstname: String,
    lastname: String
});


Each of our documents will contain a firstname and a lastname, but neither of the two fields are required. These documents will be saved to the people collection which is the plural form of our ODM model.

At this point in time MongoDB is ready to be used.

It is now time to start developing our API endpoints, so starting with the creation endpoint, we might have something like this:

server.route({
    method: "POST",
    path: "/person",
    options: {
        validate: {}
    },
    handler: async (request, h) => {
        try {
            var person = new PersonModel(request.payload);
            var result = await person.save();
            return h.response(result);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});


We’ve skipped the validation logic for now, but inside our handler we are taking the payload data sent with the request from the client and creating a new instance of our model. Using our model we can save to the database and return the response back to the client. Mongoose will do basic validation against the payload data based on the schema, but we can do better. This is where Joi comes in with Hapi.js.

Let’s look at the validate object in our route:

validate: {
    payload: {
        firstname: Joi.string().required(),
        lastname: Joi.string().required()
    },
    failAction: (request, h, error) => {
        return error.isJoi ? h.response(error.details[0]).takeover() : h.response(error).takeover();
    }
}


In the validate object, we are choosing to validate our payload. We also have the option to validate the params as well as the query of a request, but neither are necessary here. While we could do some very complex validation, we’re just validating that both properties are present. Rather than returning a vague error to the user if either are missing, we’re returning the exact error using the failAction which is optional.

Now let’s take a look at retrieving the data that had been created. In a typical CRUD scenario, we can retrieve all data or a particular piece of data. We’re going to accommodate both scenarios.

server.route({
    method: "GET",
    path: "/people",
    handler: async (request, h) => {
        try {
            var person = await PersonModel.find().exec();
            return h.response(person);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});


The above route will execute the find function in Mongoose with no query parameters. This means that there is no criteria to search for which results in all data being returned from the collection. Similarly, we could return a particular piece of data.

If we wanted to return a particular piece of data we could either provide parameters in the find function, or use the following:

server.route({
    method: "GET",
    path: "/person/{id}",
    handler: async (request, h) => {
        try {
            var person = await PersonModel.findById(request.params.id).exec();
            return h.response(person);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});


In the above endpoint we are accepting an id route parameter and are using the findById function. The data returned from the interaction is returned to the client facing application.

With the create and retrieve endpoints out of the way, we can bring this tutorial to an end with the update and delete endpoints. Starting with the update endpoint, we might have something like this:

server.route({
    method: "PUT",
    path: "/person/{id}",
    options: {
        validate: {
            payload: {
                firstname: Joi.string().optional(),
                lastname: Joi.string().optional()
            },
            failAction: (request, h, error) => {
                return error.isJoi ? h.response(error.details[0]).takeover() : h.response(error).takeover();
            }
        }
    },
    handler: async (request, h) => {
        try {
            var result = await PersonModel.findByIdAndUpdate(request.params.id, request.payload, { new: true });
            return h.response(result);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});


Just like with the create endpoint we are validating our data. However, our validation is a little different than the previous endpoint. Instead of making our properties required, we are just saying they are optional. When we do this, any property that shows up that isn’t in our validator, we will be throwing an error. So for example, if I wanted to include a middle name, it would fail.

Inside the handler function, we can use a shortcut function called findByIdAndUpdate which will allow us to find a document to update and update it in the same operation rather than doing it in two steps. We are including the new setting so that the latest document information can be returned back to the client.

The delete endpoint will be a lot simpler:

server.route({
    method: "DELETE",
    path: "/person/{id}",
    handler: async (request, h) => {
        try {
            var result = await PersonModel.findByIdAndDelete(request.params.id);
            return h.response(result);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});


Using an id parameter passed from the client, we can execute the findByIdAndDelete function which will find a document by the id, then remove it in one swoop rather than using two steps.

You should be able to play around with the API as of now. You might want to use a tool like Postman before trying to use with a frontend framework like Angular or Vue.js.

Conclusion

You just saw how to create a REST API with Hapi.js and MongoDB. While we used Mongoose and Joi to help us with the job, there are other alternatives that can be used as well.

While Hapi.js is awesome, in my opinion, if you’d like to check out how to accomplish the same using a popular framework like Express.js, you might want to check out my tutorial titled, Building a REST API with MongoDB, Mongoose, and Node.js. I’ve also written a version of this tutorial using Couchbase as the NoSQL database. That version of the tutorial can be found here.

A video version of this tutorial can be seen below.

How to create Restful CRUD API with Node.js MongoDB and Express.js

How to create Restful CRUD API with Node.js MongoDB and Express.js

How to create Restful CRUD API with Node.js MongoDB and Express.js

In this blog, we are going to learn how to perform CRUD (Create, Read, Update and Delete) operations with the help of Rest API using Node.js, MongoDB as our database, and Expess.

REST

In simple terms, REST stands for Representational State Transfer. It is an architectural style design for distributed hypermedia, or an Application Programming Interface (API). In REST, we use various standard HTTP methods like GETPOSTPUT and DELETE to perform any CRUD operation on resource.

Resource

In REST, everything is Resource. A resource can be an image, document, a temporary service, a collection of other resource, and any other object. Each resource has resource identifier to identify it.

HTTP Methods for CRUD

As per REST guidelines, we should use only HTTP methods to perform CRUD operation on any resource. In this blog, we are going to use 4 HTTP methods like GETPOSTPUT and DELETE to make our REST API.

Let’s have a brief introduction of each http method here.

  • HTTP GET

The HTTP GET is used to Read or Retrieve any resource. It returns the XML or JSON data with HTTP status code of 200. GET method is considered as safe, because we are just getting or reading the resource data, not doing any changes in the resource data.

  • HTTP GET

The HTTP POST is used to Create a new resource. On successful creation of resource, it will return HTTP status code of 201, a Location header with a link to the newly created resource.

  • HTTP GET

The HTTP PUT is used to Update any existing resource. On successful, it will return HTTP status code of 200.

  • HTTP GET

The HTTP DELETE, as the name suggests, is used to Delete any existing resource. On successful, it will return HTTP status code of 200.

Let’s move forward into the details of other pieces of creating our REST API.

Express.js

We are going to use Express.js or simply Express. It is a web application framework for Node.js. It has been released as free and open source software. You can create web application and APIs using Express. It has support for routing, middleware, view system etc.

Mongoose

Mongoose is Object Document Mapping or ODM tool for Node.js and MongoDB. Mongoose provide a straight-forward, schema based solution to model to your application data. It includes built-in type casting, validation, query building, business logic hooks and many more.

Prerequisites

You must have Node.js and MongoDB installed on your machine. Click the below links, if you don’t have any one of them.

Install Node.js

Install MongoDB

For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.
Database-as-a-Service for MongoDB

Install Postman – Google Chrome for testing purpose.

After setting up prerequisites, let move forward to build our application.

Application Introduction

In our application, we are going to create a product based application. We will use REST APIs to create, update, get and delete the product. Let’s go to create our application.

1. Create package.json

Let’s create a folder, and start with creating package.json file first. Use this command in your terminal window.

For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.#### package.json

{
  "name": "product-app",
  "version": "1.0.0",
  "description": "This is zepbook product app",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "ZeptoBook",
  "license": "MIT"
}

If you notice line 5, we have defined server.js as our main entry point.

2. Install Packages

Let’s install all the expressmongoose and body-parser package dependencies in our app.

For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.
Once these packages installed successfully, our package.json file will be updated automatically. Our latest file will be like this.

package.json

{
  "name": "product-app",
  "version": "1.0.0",
  "description": "This is zepbook product app",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "ZeptoBook",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.18.3",
    "express": "^4.16.4",
    "mongoose": "^5.4.2"
  }
}

Notice dependencies section of our file, all these packages are mentioned there.

3. Creating Our Server

Let’s create a server.js file in the root directory of the application.

server.js

// get dependencies
const express = require('express');
const bodyParser = require('body-parser');

const app = express();

// parse requests
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())

// default route
app.get('/', (req, res) => {
    res.json({"message": "Welcome to ZeptoBook Product app"});
});

// listen on port 3000
app.listen(3000, () => {
    console.log("Server is listening on port 3000");
});

Let’s briefly review our above code. First of all, we imported the required dependencies in our server.js file.

body-parser

It is Node.js body parser middleware. It parse the incoming request bodies in a middleware before your handlers, available under the req.body property.

Learn more about bodyParser.urlencoded([options])

Learn more about bodyParser.json([options])

Then, we define a default route using GET Http method. By default, it will return our message on default url.

Finally, we are going to listen all incoming requests on port 3000.

4. Run the server

Once everything is all set, let’s wake up our server by running this command in our terminal window.

For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.> For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.####

5. Create Configuration file

Let’s create a config file in our app, where we can define various constants like dbconnection or port number instead of hard-coded it everywhere. So, create a config.js file in your app folder.

config.js

module.exports = {    url: 'mongodb://<dbUserName>:<dbUserPassword>@ds251002.mlab.com:51002/adeshtestdb',    
serverport: 3000 }

So, here I mentioned two constants in config.js file.

  • HTTP GET

6. Connecting to Database

Let’s connect with our MongoDb database. Add these lines of codes in server.js file after the app.use(bodyParser.json());

// Configuring the database
const config = require('./config.js');
const mongoose = require('mongoose');

mongoose.Promise = global.Promise;

// Connecting to the database
mongoose.connect(config.url, {
    useNewUrlParser: true
}).then(() => {
    console.log("Successfully connected to the database");    
}).catch(err => {
    console.log('Could not connect to the database. Exiting now...', err);
    process.exit();
});

Also, we are going to replace our hard-coded server port with our config constant in server.js file

// listen on port 3000
app.listen(config.serverport, () => {
    console.log("Server is listening on port 3000");
});

Here, you can see, we are now using config.serverport in app.listen().

Now, run again the server using this command.

For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.> For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.> For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.####

7. Creating Product Model

Let’s create a product model in our app folder in order to save the data in our db. Create a product.model.js file in your app.

product.model.js

const mongoose = require('mongoose');

const ProductSchema = mongoose.Schema({
    title: String,
    description: String,
    price: Number,
    company: String
}, {
    timestamps: true
});

module.exports = mongoose.model('Products', ProductSchema);

Here, we have defined our ProductSchema with following properties. Along with this, we also set timestampsproperty to true. This property will add two fields automatically to schema. These fields are : createdAt and updatedAt in your schema.

8. Creating our Controller’s functions

We are going to write all functions related to create, retrieve, update and delete products in our controller file. Let’s create a controller file named product.controller.js in your app folder.

product.controller.js

const Product = require('./product.model.js');

//Create new Product
exports.create = (req, res) => {
    // Request validation
    if(!req.body) {
        return res.status(400).send({
            message: "Product content can not be empty"
        });
    }

    // Create a Product
    const product = new Product({
        title: req.body.title || "No product title", 
        description: req.body.description,
        price: req.body.price,
        company: req.body.company
    });

    // Save Product in the database
    product.save()
    .then(data => {
        res.send(data);
    }).catch(err => {
        res.status(500).send({
            message: err.message || "Something wrong while creating the product."
        });
    });
};

// Retrieve all products from the database.
exports.findAll = (req, res) => {
    Product.find()
    .then(products => {
        res.send(products);
    }).catch(err => {
        res.status(500).send({
            message: err.message || "Something wrong while retrieving products."
        });
    });
};

// Find a single product with a productId
exports.findOne = (req, res) => {
    Product.findById(req.params.productId)
    .then(product => {
        if(!product) {
            return res.status(404).send({
                message: "Product not found with id " + req.params.productId
            });            
        }
        res.send(product);
    }).catch(err => {
        if(err.kind === 'ObjectId') {
            return res.status(404).send({
                message: "Product not found with id " + req.params.productId
            });                
        }
        return res.status(500).send({
            message: "Something wrong retrieving product with id " + req.params.productId
        });
    });
};

// Update a product
exports.update = (req, res) => {
    // Validate Request
    if(!req.body) {
        return res.status(400).send({
            message: "Product content can not be empty"
        });
    }

    // Find and update product with the request body
    Product.findByIdAndUpdate(req.params.productId, {
        title: req.body.title || "No product title", 
        description: req.body.description,
        price: req.body.price,
        company: req.body.company
    }, {new: true})
    .then(product => {
        if(!product) {
            return res.status(404).send({
                message: "Product not found with id " + req.params.productId
            });
        }
        res.send(product);
    }).catch(err => {
        if(err.kind === 'ObjectId') {
            return res.status(404).send({
                message: "Product not found with id " + req.params.productId
            });                
        }
        return res.status(500).send({
            message: "Something wrong updating note with id " + req.params.productId
        });
    });
};

// Delete a note with the specified noteId in the request
exports.delete = (req, res) => {
    Product.findByIdAndRemove(req.params.productId)
    .then(product => {
        if(!product) {
            return res.status(404).send({
                message: "Product not found with id " + req.params.productId
            });
        }
        res.send({message: "Product deleted successfully!"});
    }).catch(err => {
        if(err.kind === 'ObjectId' || err.name === 'NotFound') {
            return res.status(404).send({
                message: "Product not found with id " + req.params.productId
            });                
        }
        return res.status(500).send({
            message: "Could not delete product with id " + req.params.productId
        });
    });
};

9. Defining Product API’s Routes

Next step is to create our api routes. Create a product.routes.js file in your app folder.

product.routes.js

module.exports = (app) => {
    const products = require('./product.controller.js');

    // Create a new Product
    app.post('/products', products.create);

    // Retrieve all Products
    app.get('/products', products.findAll);

    // Retrieve a single Product with productId
    app.get('/products/:productId', products.findOne);

    // Update a Note with productId
    app.put('/products/:productId', products.update);

    // Delete a Note with productId
    app.delete('/products/:productId', products.delete);
}

Note: import this route file in our server.js file after these lines. See line 4 in the below code.

// Configuring the database
const config = require('./config.js');
const mongoose = require('mongoose');
require('./product.routes.js')(app);  //Add route file here

10. Enable the CORS

If you try to access your api routes through your client-side app, you might face Access-Control-Allow-Originerror messages. So, in order to avoid these message, we are also enabling CORS in our server.js file.

server.js

//Enable CORS for all HTTP methods
app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
  });

So, this will be our final server.js file.

server.js

// get dependencies
const express = require('express');
const bodyParser = require('body-parser');

const app = express();

// parse requests
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

//Enable CORS for all HTTP methods
app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
  });

// Configuring the database
const config = require('./config.js');
const mongoose = require('mongoose');
require('./product.routes.js')(app);

mongoose.Promise = global.Promise;

// Connecting to the database
mongoose.connect(config.url, {
    useNewUrlParser: true
}).then(() => {
    console.log("Successfully connected to the database");    
}).catch(err => {
    console.log('Could not connect to the database. Exiting now...', err);
    process.exit();
});

// default route
app.get('/', (req, res) => {
    res.json({"message": "Welcome to ZeptoBook Product app"});
});

// listen on port 3000
app.listen(config.serverport, () => {
    console.log("Server is listening on port 3000");
});

This will be our project structure.

                               ![](https://i2.wp.com/www.zeptobook.com/wp-content/uploads/2019/01/Product-App-folder.png?resize=495%2C395&ssl=1)

Testing our REST APIs

Now, it’s time to test our all REST APIs for CRUD Operation.

  • HTTP GET

I have added few products in the database. See the below screen shot.

                              ![](https://i0.wp.com/www.zeptobook.com/wp-content/uploads/2019/01/List-of-Products.png?fit=519%2C1024&ssl=1) 
  • HTTP GET

  • HTTP GET

  • HTTP GET

Before Update

After Update

  • HTTP GET

How to build blazing fast REST APIs with Node.js, MongoDB, Fastify and Swagger

How to build blazing fast REST APIs with Node.js, MongoDB, Fastify and Swagger

Presumably no web developer is a stranger to REST APIs and the challenges that architecting an effective and efficient API solution brings...

Presumably no web developer is a stranger to REST APIs and the challenges that architecting an effective and efficient API solution brings...

These challenges include:
  • Speed (API Response Times)
  • Documentation (Clear concise documents, describing the API)
  • Architecture and Sustainability (Maintainable and expandable codebase)

In this tutorial we are going to address all of the above using a combination of Node.jsMongoDBFastify and Swagger.

The source code for the project is available on GitHub.

Before we begin…

You should have some beginner/intermediate JavaScript knowledge, have heard of Node.js and MongoDB, and know what REST APIs are.

Below are some links to get you updated:

  • Speed (API Response Times)
  • Documentation (Clear concise documents, describing the API)
  • Architecture and Sustainability (Maintainable and expandable codebase)

The Technology we will be using:

  • Speed (API Response Times)
  • Documentation (Clear concise documents, describing the API)
  • Architecture and Sustainability (Maintainable and expandable codebase)

It is a good idea to open the above pages in new tabs, for easy reference.

You will need to have the following installed:
  • Speed (API Response Times)
  • Documentation (Clear concise documents, describing the API)
  • Architecture and Sustainability (Maintainable and expandable codebase)

You will also need an IDE and a terminal, I use iTerm2 for Mac and Hyper for Windows.

Let’s get started!

Initialise a new project by opening your terminal, executing each of the following lines of code:

mkdir fastify-api
cd fastify-api
mkdir src
cd src
touch index.js
npm init

In the above code, we created two new directories, navigated into them, created an index.js file and initialed our project via npm.

You will be prompted to enter several values when initialising a new project, these you can leave blank and update at a later stage.

Once completed, a package.json file is generated in the src directory. In this file you can change the values entered when the project was initialised.

Next we install all the dependancies that we will need:

npm i nodemon mongoose fastify fastify-swagger boom

Below is a brief description of what each package does, quoted from their respective websites:

nodemon

nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.> nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.
To set up nodemon, we need to add the following line of code to our package.json file, in the scripts object:

“start”: “./node_modules/nodemon/bin/nodemon.js ./src/index.js”,

Our package.json file should now look as follows:

{
  "name": "fastify-api",
  "version": "1.0.0",
  "description": "A blazing fast REST APIs with Node.js, MongoDB, Fastify and Swagger.",
  "main": "index.js",
  "scripts": {
  "start": "./node_modules/nodemon/bin/nodemon.js ./src/index.js",
  "test": "echo \"Error: no test specified\" && exit 1"
},
  "author": "Siegfried Grimbeek <[email protected]> (www.siegfriedgrimbeek.co.za)",
  "license": "ISC",
  "dependencies": {
  "boom": "^7.2.2",
  "fastify": "^1.13.0",
  "fastify-swagger": "^0.15.3",
  "mongoose": "^5.3.14",
  "nodemon": "^1.18.7"
  }
}

mongoose

nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.
fastify
nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.
fastify-swagger
nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.
boom
nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.### Setup up the server and create the first route!

Add the following code to your index.js file:

// Require the framework and instantiate it
const fastify = require('fastify')({
  logger: true
})

// Declare a route
fastify.get('/', async (request, reply) => {
  return { hello: 'world' }
})

// Run the server!
const start = async () => {
  try {
    await fastify.listen(3000)
    fastify.log.info(`server listening on ${fastify.server.address().port}`)
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

We require the Fastify framework, declare our first route and initialise the server on port 3000, the code is pretty self explanatory but take note of the options object passed when initialising Fastify:

// Require the fastify framework and instantiate it
const fastify = require('fastify')({
  logger: true
})

The above code enables Fastify’s built in logger which is disabled by default.

You can now run the follow code in your src directory in your terminal:

npm start

Now when you navigate to http://localhost:3000/ you should see the {hello:world} object returned.

We will get back to the index.js file but for now let’s move on to setting up our database.

Start MongoDB and create the model!

Once MongoDB has been successfully installed, you can open a new terminal window and start up a MongoDBinstance by running the following:

mongod

With MongoDB, we do not need to create a database. We can just specify a name in the setup and as soon as we store data, MongoDB will create this database for us.

Add the following to your index.js file:

...
// Require external modules
const mongoose = require('mongoose')
// Connect to DB
mongoose.connect(‘mongodb://localhost/mycargarage’)
 .then(() => console.log(‘MongoDB connected…’))
 .catch(err => console.log(err))
...

In the above code we require Mongoose and connect to our MongoDB database. The database is called mycargarage and if all went well, you will now see MongoDB connected... in your terminal.

Notice that you did not have to restart the app, thanks to the _Nodemon_ package that we added earlier.

Now that our database is up and running, we can create our first Model. Create a new folder within the src directory called models, and within it create a new file called Car.js and add the following code:

// External Dependancies
const mongoose = require('mongoose')

const carSchema = new mongoose.Schema({
  title: String,
  brand: String,
  price: String,
  age: Number,
  services: {
    type: Map,
    of: String
  }
})

module.exports = mongoose.model('Car', carSchema)

The above code declares our carSchema that contains all the information related to our cars. Apart from the two obvious data types: String and Number. We also make use of a Map which is relatively new to Mongoose and you can read more about it here. We then export our carSchema to be used within our app.

We could proceed with setting up our routes, controllers and config in the index.js file, but part of this tutorial is demonstrating a sustainable codebase. Therefore each component will have its own folder.

Create the car controller

To get started with creating the controllers, we create a folder in the src directory called controllers, and within the folder, we create a carController.js file:

// External Dependancies
const boom = require('boom')

// Get Data Models
const Car = require('../models/Car')

// Get all cars
exports.getCars = async (req, reply) => {
  try {
    const cars = await Car.find()
    return cars
  } catch (err) {
    throw boom.boomify(err)
  }
}

// Get single car by ID
exports.getSingleCar = async (req, reply) => {
  try {
    const id = req.params.id
    const car = await Car.findById(id)
    return car
  } catch (err) {
    throw boom.boomify(err)
  }
}

// Add a new car
exports.addCar = async (req, reply) => {
  try {
    const car = new Car(req.body)
    return car.save()
  } catch (err) {
    throw boom.boomify(err)
  }
}

// Update an existing car
exports.updateCar = async (req, reply) => {
  try {
    const id = req.params.id
    const car = req.body
    const { ...updateData } = car
    const update = await Car.findByIdAndUpdate(id, updateData, { new: true })
    return update
  } catch (err) {
    throw boom.boomify(err)
  }
}

// Delete a car
exports.deleteCar = async (req, reply) => {
  try {
    const id = req.params.id
    const car = await Car.findByIdAndRemove(id)
    return car
  } catch (err) {
    throw boom.boomify(err)
  }
}

The above may seem like a little much to take in, but it is actually really simple.

  • Speed (API Response Times)
  • Documentation (Clear concise documents, describing the API)
  • Architecture and Sustainability (Maintainable and expandable codebase)

Other than that, we make use of some standard Mongoose features used to manipulate our database.

You are probably burning to fire up your API and do a sanity check, but before we do this, we just need to connect the controller to the routes and then lastly connect the routes to the app.

Create and import the routes

Once again, we can start by creating a folder in the root directory of our project, but this time, it is called routes. Within the folder, we create an index.js file with the following code:

// Import our Controllers
const carController = require('../controllers/carController')

const routes = [
  {
    method: 'GET',
    url: '/api/cars',
    handler: carController.getCars
  },
  {
    method: 'GET',
    url: '/api/cars/:id',
    handler: carController.getSingleCar
  },
  {
    method: 'POST',
    url: '/api/cars',
    handler: carController.addCar,
    schema: documentation.addCarSchema
  },
  {
    method: 'PUT',
    url: '/api/cars/:id',
    handler: carController.updateCar
  },
  {
    method: 'DELETE',
    url: '/api/cars/:id',
    handler: carController.deleteCar
  }
]

module.exports = routes

Here we are requiring our controller and assigning each of the functions that we created in our controller to our routes.

As you can see, each route consists out of a method, a url and a handler, instructing the app on which function to use when one of the routes is accessed.

The :id following some of the routes is a common way to pass parameters to the routes, and this will allow us to access the id as follows:

[[http://127.0.0.1:3000/api/cars/5bfe30b46fe410e1cfff2323](http://127.0.0.1:3000/api/cars/5bfe30b46fe410e1cfff2323)](http://127.0.0.1:3000/api/cars/5bfe30b46fe410e1cfff2323](http://127.0.0.1:3000/api/cars/5bfe30b46fe410e1cfff2323) "http://127.0.0.1:3000/api/cars/5bfe30b46fe410e1cfff2323](http://127.0.0.1:3000/api/cars/5bfe30b46fe410e1cfff2323)")

Putting it all together and testing our API

Now that we have most of our parts constructed, we just need to connect them all together to start serving data via our API. Firstly we need to import our routes that we created by adding the following line of code to our main index.js file:

const routes = require(‘./routes’)

We then need to loop over our routes array to initialise them with Fastify. We can do this with the following code, which also needs to be added to the main index.js file:

routes.forEach((route, index) => {
 fastify.route(route)
})

Now we are ready to start testing!

The best tool for the job is Postman, which we will use to test all of our routes. We will be sending our data as raw objects in the body of the request and as parameters.

Finding all cars:

Finding a single car:

Adding a new car**:

** The services appear to be empty, but the information does in fact persist to the database.

Updating a car:

Deleting a car:

We now have a fully functional API — but what about the documentation? This is where Swagger is really handy.

Adding Swagger and wrapping up.

Now we will create our final folder called config. Inside we will create a file called swagger.js with the following code:

exports.options = {
  routePrefix: '/documentation',
  exposeRoute: true,
  swagger: {
    info: {
      title: 'Fastify API',
      description: 'Building a blazing fast REST API with Node.js, MongoDB, Fastify and Swagger',
      version: '1.0.0'
    },
    externalDocs: {
      url: 'https://swagger.io',
      description: 'Find more info here'
    },
    host: 'localhost',
    schemes: ['http'],
    consumes: ['application/json'],
    produces: ['application/json']
  }
}

The above code is an object with the options which we will pass into our fastify-swagger plugin. To do this, we need to add the following to our index.js file:

// Import Swagger Options
const swagger = require(‘./config/swagger’)
// Register Swagger
fastify.register(require(‘fastify-swagger’), swagger.options)

And then we need to add the following line after we have initialised our Fastify server:

...
await fastify.listen(3000)
fastify.swagger()
fastify.log.info(`listening on ${fastify.server.address().port}`)
...

And that is it! If you now navigate to http://localhost:3000/documentation, you should see the following:

As simple as that! You now have self updating API documentation that will evolve with your API. You can easily add additional information to your routes, see more here.

Whats Next?

Now that we have a basic API in place, the possibilities are limitless. It can be used as the base for any app imaginable.

In the next tutorial, we will integrate GraphQL and eventually integrate the frontend with Vue.js too!

Learn More

☞ The Complete Node.js Developer Course (2nd Edition)

☞ Learn and Understand NodeJS

☞ Node JS: Advanced Concepts

☞ GraphQL: Learning GraphQL with Node.Js

☞ Angular (Angular 2+) & NodeJS - The MEAN Stack Guide