node-express-mongodb-crud-rest-api-feature-image

Node.js, Express & MongoDb: Build a CRUD Rest Api example

Express is one of the most popular web frameworks for Node.js that supports routing, middleware, view system… Mongoose is a promise-based Node.js ODM for MongoDB that provides a straight-forward, schema-based solution to model our application data along with built-in type casting, validation, query building, business logic hooks… In this tutorial, I will show you step by step to build Node.js Restful API for CRUD operations using Express, Mongoose with MongoDB database.

You should install MongoDB in your machine first. The installation instructions can be found at Official MongoDB installation manual.

Fullstack:
– MEVN: Vue.js + Node.js + Express + MongoDB example
– MEAN: Angular + Node.js + Express + MongoDB example
– MERN: React + Node.js + Express + MongoDB example

Security: Node.js + MongoDB: User Authentication & Authorization with JWT

Node.js MongoDB Rest CRUD API overview

We will build Rest Apis that can create, retrieve, update, delete and find Tutorials by title.

First, we start with an Express web server. Next, we add configuration for MongoDB database, create Tutorial model with Mongoose, write the controller. Then we define routes for handling all CRUD operations (including custom finder).

The following table shows overview of the Rest APIs that will be exported:

MethodsUrlsActions
GETapi/tutorialsget all Tutorials
GETapi/tutorials/:idget Tutorial by id
POSTapi/tutorialsadd new Tutorial
PUTapi/tutorials/:idupdate Tutorial by id
DELETEapi/tutorials/:idremove Tutorial by id
DELETEapi/tutorialsremove all Tutorials
GETapi/tutorials/publishedfind all published Tutorials
GETapi/tutorials?title=[kw]find all Tutorials which title contains 'kw'

Finally, we’re gonna test the Rest Apis using Postman.

This is our project structure:

node-express-mongodb-crud-rest-api-project-structure

Demo Video

This is our Node.js Express MongoDB application brief instruction and demo, test Rest Apis with Postman.

Create Node.js App

First, we create a folder:

$ mkdir nodejs-express-mongodb
$ cd nodejs-express-mongodb

Next, we initialize the Node.js App with a package.json file:

npm init

name: (nodejs-express-mongodb) 
version: (1.0.0) 
description: Node.js Restful CRUD API with Node.js, Express and MongoDB
entry point: (index.js) server.js
test command: 
git repository: 
keywords: nodejs, express, mongodb, rest, api
author: bezkoder
license: (ISC)

Is this ok? (yes) yes

We need to install necessary modules: express, mongoose, body-parser and cors.
Run the command:

npm install express mongoose body-parser cors --save

The package.json file should look like this:

{
  "name": "node-express-mongodb",
  "version": "1.0.0",
  "description": "Node.js Restful CRUD API with Node.js, Express and MongoDB",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "nodejs",
    "express",
    "rest",
    "api",
    "mongodb"
  ],
  "author": "bezkoder",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "mongoose": "^5.8.10"
  }
}

Setup Express web server

In the root folder, let’s create a new server.js file:

const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");

const app = express();

var corsOptions = {
  origin: "http://localhost:8081"
};

app.use(cors(corsOptions));

// parse requests of content-type - application/json
app.use(bodyParser.json());

// parse requests of content-type - application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }));

// simple route
app.get("/", (req, res) => {
  res.json({ message: "Welcome to bezkoder application." });
});

// set port, listen for requests
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}.`);
});

What we do are:
– import express, body-parser and cors modules:

  • Express is for building the Rest apis
  • body-parser helps to parse the request and create the req.body object
  • cors provides Express middleware to enable CORS with various options.

– create an Express app, then add body-parser and cors middlewares using app.use() method. Notice that we set origin: http://localhost:8081.
– define a GET route which is simple for test.
– listen on port 8080 for incoming requests.

Now let’s run the app with command: node server.js.
Open your browser with url http://localhost:8080/, you will see:

node-js-express-mongodb-example-setup-server

Yeah, the first step is done. We’re gonna work with Mongoose in the next section.

Configure MongoDB database & Mongoose

In the app folder, we create a separate config folder for configuration with db.config.js file like this:

module.exports = {
  url: "mongodb://localhost:27017/bezkoder_db"
};

Define Mongoose

We’re gonna define Mongoose model (tutorial.model.js) also in app/models folder in the next step.

Now create app/models/index.js with the following code:

const dbConfig = require("../config/db.config.js");

const mongoose = require("mongoose");
mongoose.Promise = global.Promise;

const db = {};
db.mongoose = mongoose;
db.url = dbConfig.url;
db.tutorials = require("./tutorial.model.js")(mongoose);

module.exports = db;

Don’t forget to call connect() method in server.js:

...
const app = express();
app.use(...);

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();
  });

Define the Mongoose Model

In models folder, create tutorial.model.js file like this:

module.exports = mongoose => {
  const Tutorial = mongoose.model(
    "tutorial",
    mongoose.Schema(
      {
        title: String,
        description: String,
        published: Boolean
      },
      { timestamps: true }
    )
  );

  return Tutorial;
};

This Mongoose Model represents tutorials collection in MongoDB database. These fields will be generated automatically for each Tutorial document: _id, title, description, published, createdAt, updatedAt, __v.

{
  "_id": "5e363b135036a835ac1a7da8",
  "title": "Js Tut#",
  "description": "Description for Tut#",
  "published": true,
  "createdAt": "2020-02-02T02:59:31.198Z",
  "updatedAt": "2020-02-02T02:59:31.198Z",
  "__v": 0
}

If you use this app with a front-end that needs id field instead of _id, you have to override toJSON method that map default object to a custom object. So the Mongoose model could be modified as following code:

module.exports = mongoose => {
  var schema = mongoose.Schema(
    {
      title: String,
      description: String,
      published: Boolean
    },
    { timestamps: true }
  );

  schema.method("toJSON", function() {
    const { __v, _id, ...object } = this.toObject();
    object.id = _id;
    return object;
  });

  const Tutorial = mongoose.model("tutorial", schema);
  return Tutorial;
};

And the result will look like this-

{
  "title": "Js Tut#",
  "description": "Description for Tut#",
  "published": true,
  "createdAt": "2020-02-02T02:59:31.198Z",
  "updatedAt": "2020-02-02T02:59:31.198Z",
  "id": "5e363b135036a835ac1a7da8"
}

After finishing the steps above, we don’t need to write CRUD functions, Mongoose Model supports all of them:

These functions will be used in our Controller.

Create the Controller

Inside app/controllers folder, let’s create tutorial.controller.js with these CRUD functions:

  • create
  • findAll
  • findOne
  • update
  • delete
  • deleteAll
  • findAllPublished
const db = require("../models");
const Tutorial = db.tutorials;

// Create and Save a new Tutorial
exports.create = (req, res) => {
  
};

// Retrieve all Tutorials from the database.
exports.findAll = (req, res) => {
  
};

// Find a single Tutorial with an id
exports.findOne = (req, res) => {
  
};

// Update a Tutorial by the id in the request
exports.update = (req, res) => {
  
};

// Delete a Tutorial with the specified id in the request
exports.delete = (req, res) => {
  
};

// Delete all Tutorials from the database.
exports.deleteAll = (req, res) => {
  
};

// Find all published Tutorials
exports.findAllPublished = (req, res) => {
  
};

Let’s implement these functions.

Create a new object

Create and Save a new Tutorial:

exports.create = (req, res) => {
  // Validate request
  if (!req.body.title) {
    res.status(400).send({ message: "Content can not be empty!" });
    return;
  }

  // Create a Tutorial
  const tutorial = new Tutorial({
    title: req.body.title,
    description: req.body.description,
    published: req.body.published ? req.body.published : false
  });

  // Save Tutorial in the database
  tutorial
    .save(tutorial)
    .then(data => {
      res.send(data);
    })
    .catch(err => {
      res.status(500).send({
        message:
          err.message || "Some error occurred while creating the Tutorial."
      });
    });
};

Retrieve objects (with condition)

Retrieve all Tutorials/ find by title from the database:

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:
          err.message || "Some error occurred while retrieving tutorials."
      });
    });
};

We use req.query.title to get query string from the Request and consider it as condition for findAll() method.

Retrieve a single object

Find a single Tutorial with an id:

exports.findOne = (req, res) => {
  const id = req.params.id;

  Tutorial.findById(id)
    .then(data => {
      if (!data)
        res.status(404).send({ message: "Not found Tutorial with id " + id });
      else res.send(data);
    })
    .catch(err => {
      res
        .status(500)
        .send({ message: "Error retrieving Tutorial with id=" + id });
    });
};

Update an object

Update a Tutorial identified by the id in the request:

exports.update = (req, res) => {
  if (!req.body) {
    return res.status(400).send({
      message: "Data to update can not 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 Tutorial with id=${id}. Maybe Tutorial was not found!`
        });
      } else res.send({ message: "Tutorial was updated successfully." });
    })
    .catch(err => {
      res.status(500).send({
        message: "Error updating Tutorial with id=" + id
      });
    });
};

Delete an object

Delete a Tutorial with the specified id:

exports.delete = (req, res) => {
  const id = req.params.id;

  Tutorial.findByIdAndRemove(id)
    .then(data => {
      if (!data) {
        res.status(404).send({
          message: `Cannot delete Tutorial with id=${id}. Maybe Tutorial was not found!`
        });
      } else {
        res.send({
          message: "Tutorial was deleted successfully!"
        });
      }
    })
    .catch(err => {
      res.status(500).send({
        message: "Could not delete Tutorial with id=" + id
      });
    });
};

Delete all objects

Delete all Tutorials from the database:

exports.deleteAll = (req, res) => {
  Tutorial.deleteMany({})
    .then(data => {
      res.send({
        message: `${data.deletedCount} Tutorials were deleted successfully!`
      });
    })
    .catch(err => {
      res.status(500).send({
        message:
          err.message || "Some error occurred while removing all tutorials."
      });
    });
};

Find all objects by condition

Find all Tutorials with published = true:

exports.findAllPublished = (req, res) => {
  Tutorial.find({ published: true })
    .then(data => {
      res.send(data);
    })
    .catch(err => {
      res.status(500).send({
        message:
          err.message || "Some error occurred while retrieving tutorials."
      });
    });
};

Define Routes

When a client sends request for an endpoint using HTTP request (GET, POST, PUT, DELETE), we need to determine how the server will reponse by setting up the routes.

These are our routes:

  • /api/tutorials: GET, POST, DELETE
  • /api/tutorials/:id: GET, PUT, DELETE
  • /api/tutorials/published: GET

Create a turorial.routes.js inside app/routes folder with content like this:

module.exports = app => {
  const tutorials = require("../controllers/tutorial.controller.js");

  var router = require("express").Router();

  // Create a new Tutorial
  router.post("/", tutorials.create);

  // Retrieve all Tutorials
  router.get("/", tutorials.findAll);

  // Retrieve all published Tutorials
  router.get("/published", tutorials.findAllPublished);

  // Retrieve a single Tutorial with id
  router.get("/:id", tutorials.findOne);

  // Update a Tutorial with id
  router.put("/:id", tutorials.update);

  // Delete a Tutorial with id
  router.delete("/:id", tutorials.delete);

  // Create a new Tutorial
  router.delete("/", tutorials.deleteAll);

  app.use('/api/tutorials', router);
};

You can see that we use a controller from /controllers/tutorial.controller.js.

We also need to include routes in server.js (right before app.listen()):

...

require("./app/routes/turorial.routes")(app);

// set port, listen for requests
const PORT = ...;
app.listen(...);

Test the APIs

Run our Node.js application with command: node server.js.

Using Postman, we’re gonna test all the Apis above.

  1. Create a new Tutorial using POST /tutorials Api

  2. node-express-mongodb-crud-rest-api-create

    After creating some new Tutorials, you can check MongoDb collection:

    node-express-mongodb-crud-rest-api-database-create

  3. Retrieve a single Tutorial by id using GET /tutorials/:id Api

  4. node-express-mongodb-crud-rest-api-find-by-id

  5. Update a Tutorial using PUT /tutorials/:id Api

  6. node-express-mongodb-crud-rest-api-update

    Check tutorials collection after some documents were updated:

    node-express-mongodb-crud-rest-api-database-update

  7. Find all Tutorials which title contains ‘js’: GET /tutorials?title=js

  8. node-express-mongodb-crud-rest-api-find-by-field

  9. Find all published Tutorials using GET /tutorials/published Api

  10. node-express-mongodb-crud-rest-api-find-by-active

  11. Delete a Tutorial using DELETE /tutorials/:id Api

  12. node-express-mongodb-crud-rest-api-delete

    The Tutorial was removed from tutorials collection:

    node-express-mongodb-crud-rest-api-database-delete

  13. Delete all Tutorials using DELETE /tutorials Api

  14. node-express-mongodb-crud-rest-api-delete-all

Conclusion

Today, we’ve learned how to create Node.js Rest Apis with an Express web server. We also know way to add configuration for MongoDB database & Mongoose, create a Mongoose Model, write a controller and define routes for handling all CRUD operations.

You can also find how to build a fullstack system with this app as backend server in the post:
– MEVN: Vue.js + Node.js + Express + MongoDB example
– MEAN: Angular + Node.js + Express + MongoDB example
– MERN: React + Node.js + Express + MongoDB example

Happy learning! See you again.

Further Reading

Source code

You can find the complete source code for this example on Github.

39 thoughts to “Node.js, Express & MongoDb: Build a CRUD Rest Api example”

  1. Thank you, man!
    Awesome post!

    PS: There is a typo in ‘turorial.routes.js’ filename, i’ve created it like tutorial but copied your code and a error appeared that tutorial.routes.js file is not found. It took me a couple of minutes to find out that the problem is in tuRorial))

  2. When I try to do an update, the message is ok “Updated successfully”, but no changes is done in the database – where should I look for the problem?

  3. Hello!
    First, thank you for the tutorial!

    Every time I try to create a “new tutorial”, postman return “Content can not be empty”.
    I’ve triple checked that I wrote “title” as title everywhere and it seems that way. I’m a bit at lost as to where to look next ?

  4. Thanks for the tutorial!

    Every time I try to create a “new tutorial”, postman return “Content can not be empty”. As a last try , i’ve downloaded the project from github, I still have the same issue . I have no idea left as to where to check

    1. Hi, please make sure that you’ve set Content-Type : application/json in HTTP request header (Postman Header tab).

  5. hello I did everything as in tutorial, I get connected to the database and everything, but when I test in Postman I get this error: Cannot POST /api/tutorials

    the same for other API test

        1. Hi, please make sure that you’ve set Content-Type : application/json in HTTP request header (Postman Header tab).

  6. hello please i need your help: i got this from postman
    {
    “message”: “Content can not be empty!”
    }
    how can i solve it

  7. Thank you for this tutorial. I’ve just started learning node.js and it helps me a lot to see how a basic app is set up.

  8. wahoo very clear, thanks.
    question :
    do you have any tutorial on nodejs+express+vuejs+cassandra?
    if yes please share the link

  9. its great tutorial, but ive some problem :
    TypeError: Tutorial.find is not a function
    at exports.findAll (D:\Faisal\web\Dev\nem-api\app\controllers\tutorial.controller.js:36:14)
    at Layer.handle [as handle_request] (D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\layer.js:95:5)
    at next (D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\route.js:137:13)
    at Route.dispatch (D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\route.js:112:3)
    at Layer.handle [as handle_request] (D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\layer.js:95:5)
    at D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\index.js:281:22
    at Function.process_params (D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\index.js:335:12)
    at next (D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\index.js:275:10)
    at Function.handle (D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\index.js:174:3)
    at router (D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\index.js:47:12)
    at Layer.handle [as handle_request] (D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\layer.js:95:5)
    at trim_prefix (D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\index.js:317:13)
    at D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\index.js:284:7
    at Function.process_params (D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\index.js:335:12)
    at next (D:\Faisal\web\Dev\nem-api\node_modules\express\lib\router\index.js:275:10)
    at urlencodedParser (D:\Faisal\web\Dev\nem-api\node_modules\body-parser\lib\types\urlencoded.js:91:7)

  10. Really, really cool way to structure things. Very efficient and maintainable as the codebase grows. Thank you for this!!

  11. Why are you showing me this error?
    {
    “message”: “Cast to ObjectId failed for value \”published\” at path \”_id\” for model \”Tutorial\””
    }

    The solution I found was modifying my routes file:
    From this:

    router.get('/:id', tutorial.findOne);
    router.put('/:id', tutorial.updateTutorial);
    router.delete('/:id', tutorial.deleteTutorial);
    router.get('/published', tutorial.findAllPublished);
    

    To this:

    router.get('/published', tutorial.findAllPublished);
    router.get('/:id', tutorial.findOne);
    router.put('/:id', tutorial.updateTutorial);
    router.delete('/:id', tutorial.deleteTutorial);
    

    But if I want to know what is the reason for this error. Thanks

  12. thanks for this wonderful tutorial

    i have i ssue when i try to search with id

    Eror Message:
    Cannot GET /api/tutorials/5ec7c48900ba2804d0b29ca6

Leave a Reply

Your email address will not be published. Required fields are marked *