DEV Community

Cover image for Build a Node.js/Express API with MongoDB
Edin
Edin

Posted on • Edited on

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 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
Enter fullscreen mode Exit fullscreen mode

Now initialize package.json

npm init
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

npm install --save-dev nodemon
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}`);
});
Enter fullscreen mode Exit fullscreen mode

run the app in the terminal:

node app.js
Enter fullscreen mode Exit fullscreen mode

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!")
})
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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!")
})
Enter fullscreen mode Exit fullscreen mode

and import our dependencies like this:

const mongoose = require("mongoose");
const cors = require("cors");
Enter fullscreen mode Exit fullscreen mode

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 }
);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 }
);
Enter fullscreen mode Exit fullscreen mode

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}`);
});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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!");
  });
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
  });
};
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

and in book.controller.js

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

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);
Enter fullscreen mode Exit fullscreen mode

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.");
  });
};
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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");
  });
};
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (7)

Collapse
 
thedvlpr profile image
Sophie • Edited

Hello Edin!

Thank you so much for the tutorial. I ran into an error and would be obliged if you could help me.
I got TypeError: Book is not a constructor in this piece of code (pls see screenshot - dev-to-uploads.s3.amazonaws.com/i/...) and cannot move further with the tutorial.
Thanks in advance.

Collapse
 
edin profile image
Edin • Edited

Add the line

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

to book.model.js

I fixed it above, sorry for that.

Collapse
 
thedvlpr profile image
Sophie

Thanks for your prompt reply Edin! I appreciate that.

One more thing, Edin, would you pls fix this part of code, otherwise someone will run into a ReferenceError: next is not defined :-) Thanks again!


book.save((next, err) => {
        if (err) {
            return next(err);
        }
        res.send('Book created successfully!');
    });
Collapse
 
cadd profile image
Robert

Just as a remark: You don't need bodyparser as a dependency, it' build into express: app.use(express.json());

Collapse
 
edin profile image
Edin • Edited

It's fixed, thanks.

Collapse
 
jimcmorrison profile image
JimCMorrison

Well documented workflow Edin! I will definitely give this a shot based on what you've put together here. I will let you know if I run into any challenges!

Collapse
 
andrewbaisden profile image
Andrew Baisden

Awesome mongoose just makes the whole process super simple.