Build a Node.js/Express API with MongoDB

In this tutorial we will create a Book Collection API, you will be able to Create Read Update and Delete Books, or in short perform CRUD operations.

This tutorial is aimed at beginners so there will be some additional explanations and links to sources that might be helpful.

Necessary tools:

Lets set up MongoDB first, it will take us a minute and we can focus on writing code then:

This step doesn't require a credit card and the free tier is more than enough for our project!

Login to your MongoDB account and create a cluster.

You don't need to pick the same region, feel free to pick whatever suits you. It doesn't really matter in this tutorial. cluster creation

After you created your cluster go to Collections Since you probably just created the account its empty and you will be greeted by a message like this:

eplore your data

Click on Add My Own Data

  • Create Database - if you already have databases in your cluster.

I'll name the Database "Books-API" and the collection "Books".

We just need to create a new user for the database and we're done here. On the left side under Security go to Database Access and then Add new user

dbuser

Enter a user name and a strong password, I'll go with the name "books-user".

The user setup is fine for this scale of project, but if you go further with this, take a look at the [MongoDB docs] (docs.atlas.mongodb.com/security-add-mongodb..) and set up user roles accordingly.

Congratulations, you have created the database and the database user

We're all set up, open a terminal in a suitable location and lets get to the fun part!

First create a folder for our project:

mkdir books-api
cd books-api

Now initialize package.json

npm init

You will be asked for a package name, version description etc, you can leave everything on default by hitting the enter key.

Next we will install all of the required dependencies:

npm install express mongoose cors dotenv --save

And a 'dev' dependency called nodemon which will restart the server for us automatically when we save our changes.

npm install --save-dev nodemon

Open up the project in your prefered text editor - I recommend VSC

First lets create a .gitignore and paste the following into it:

# Dependency directories
node_modules/
jspm_packages/

# dotenv environment variables file
.env
.env.test

the file .gitignore does what the name implies, it ignores certain folders and files when we push our project to git. In this case we want to ignore node_modules and the .env file.

Type the following in the terminal

git init

to initialize git.

You don't need to use git, but I recommend it for practical reasons and you will eventually need to learn to use it anyway.

Create a file named app.js and type the following:

const express = require("express");

const app = express();
const port = process.env.PORT || 5000;

app.listen(port, () => {
  console.log(`Server running on port: ${port}`);
});

run the app in the terminal:

node app.js

The server should be up and running and you should see Server running on port: 5000 in the terminal.

In the browser if we go to http://localhost:5000/ we see the message Cannot GET /

Lets fix that, in app.js add the line:

app.get('/', (req, res) => {
    res.send("Hello World!")
})

We will need to save the file and restart the server. In the terminal press ctrl + c to terminate the server, and run node app.js once more.

Refresh the page (http://localhost:5000/) and you should see Hello World!.

Our server works and we can start implementing the model, routes and controllers. But it would be much easier if we didn't need to restart the server after every change and re-run it. That's where our development dependency nodemon helps us. It will restart the server for us automatically so we have to run it only once and forget about it. Sounds good!

in package.json add the following line into scripts "dev": "nodemon app.js", it should look like this:

"scripts": {
  "dev": "nodemon app.js"
}

and now we can run npm run dev in the terminal and it should look like this: nodemon

In app.js.

Remove this:

app.get('/', (req, res) => {
    res.send("Hello World!")
})

and import our dependencies like this:

const mongoose = require("mongoose");
const cors = require("cors");

We need to connect MongoDB, first lets go to our browser and open our cluster on the cloud, we need to get the connection string and we do so by clicking on the connect button

connect

and in the next step click on Connect your application and you should see this screen: connectionstring

Copy the connection string and go back to app.js in VSC.

Create a connection to MongoDB by typing the following:

Its important that the connection string is the one you copied from MongoDB Cloud. It won't work with the one below

mongoose.connect(
  `mongodb+srv://books-user:<password>@cluster0.qvwwc.gcp.mongodb.net/<dbname>?retryWrites=true&w=majority`,
  { useNewUrlParser: true, useUnifiedTopology: true }
);

Replace <password> with your password (the one you used to create the user on MongoDB) and <dbname> with books.

This is the perfect opportunity to set up our dotenv. We do that by creating a file named .env in the root of our project, and add the following code:

DB_USER=db_user
DB_PASS=db_pass

Where db_user is your database username and db_pass is your database password.

And now back in app.js in our connection string we will replace the username and password and it will look something like this:

Don't forget to require and configure dotenv

require("dotenv").config();

mongoose.connect(
  `mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASS}@cluster0.qvwwc.gcp.mongodb.net/books?retryWrites=true&w=majority`,
  { useNewUrlParser: true, useUnifiedTopology: true }
);

now we need to add cors and our books route. Our final app.js will look like this:

const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");

const book = require("./routes/book.routes");

const app = express();
const port = process.env.PORT || 5000;

require("dotenv").config();

mongoose.connect(
  `mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASS}@cluster0.qvwwc.gcp.mongodb.net/books?retryWrites=true&w=majority`,
  { useNewUrlParser: true, useUnifiedTopology: true }, () => {
      console.log('MongoDB Connected')
  }
);

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use("/books", book);

app.listen(port, () => {
  console.log(`Server running on port: ${port}`);
});

In the root of the project create a controllers, models and routes folder.

In the model folder we are going to create a file named book.model.js and type the following inside it:

const mongoose = require('mongoose')

const Schema = mongoose.Schema;

const BookSchema = new Schema ({
    title: {type: String, required: true, max: 100},
    author: {type: String, required: true},
    year: {type: Number, required:true},
})

module.exports = mongoose.model("Book", BookSchema);

This is our book model.

Lets create a new file in the controllers folder and name it book.controller.js and type the following:

const Book = require("../models/book.model");

exports.book_create = (req, res, next) => {
  const book = new Book({
    title: req.body.title,
    author: req.body.author,
    year: req.body.year,
  });

  book.save((err) => {
    if (err) {
      return next(err);
    }
    res.send("Book created successfully!");
  });
};

In the routes folder create a new file and name it book.routes.js and type the following code:

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

const book_controller = require("../controllers/book.controller");

router.post("/create", book_controller.book_create);

module.exports = router;

With our server running open up Insomnia (or Postman).

Create a new POST request, and in the body select Form URL Encoded and enter the required fields. Your request should look similar to this:

req-post-create-book

Now lets check our database to confirm that it's indeed created.

mongo-confirmcreation

And there it is, our first route is done.

Now we just need to implement the rest of the functionality.

Get Book by ID

in book.routes.js add the following:

router.get("/:id", book_controller.book_details);

and in book.controller.js

exports.book_details = (req, res) => {
  Book.findById(req.params.id, (err, book) => {
    if (err) return next(err);
    res.send(book);
  });
};

Save and create a new GET request in Insomnia like this: GET http://localhost:5000/books/book_id where book_id is the id you can get from MongoDB Cloud in your DB. The request will look similar to this:

get-book-byid

Get All Books

Add the route to book.routes.js:

router.get("/", book_controller.all_books);

and in book.controller.js

exports.all_books = (req, res) => {
  Book.find({}, (err, book) => {
    if (err) return next(err);
    res.json(book);
  });
};

Save and lets test our route, in Insomnia create a GET request at http://localhost:5000/books and we should receive all our books from the collection like:

get-all

Update Book

Add the route to book.routes.js:

router.put("/:id/update", book_controller.book_update);

and in book.controller.js

exports.book_update = (req, res) => {
  Book.findByIdAndUpdate(req.params.id, { $set: req.body }, (err, book) => {
    if (err) return next(err);
    res.send("Book Udpated.");
  });
};

To test the route we will create another request in Insomnia, this time a PUT request, like this: PUT http://localhost:5000/books/id_of_book_to_be_updated/update And the body should be Form URL Encoded

book-update

We got one more route and that is:

Delete Book

Add the route to book.routes.js:

router.delete("/:id/delete", book_controller.book_delete);

and in book.controller.js

exports.book_delete = (req, res) => {
  Book.findByIdAndRemove(req.params.id, (err) => {
    if (err) return next(err);
    res.send("Book Deleted");
  });
};

Create another request in Insomnia like this: DELETE http://localhost:5000/books/id_of_book_to_be_deleted/delete

To confirm we can check our MongoDB Cloud database and we'll see that indeed everything works as expected!

If you thought that was interesting and you would like to see more tutorials like this let me know in the comments below or over on Twitter.

No Comments Yet