Build a Node.js/Express API with MongoDB
Tags: javascript, node, mongodb, tutorial
Published: 2020-09-22 12:40:07

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:

* [Node.js](https://nodejs.org/en/)
* [Insomnia](https://insomnia.rest/)
* [MongoDB](https://www.mongodb.com/) - Create a free account (no installation required)
* [Visual Studio Code](https://code.visualstudio.com/) or any text editor that you prefer.

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](https://www.mongodb.com/) 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](https://dev-to-uploads.s3.amazonaws.com/i/ud1v7zpq449lo3pq5vur.jpg)

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](https://dev-to-uploads.s3.amazonaws.com/i/98dp59uhpq3m625bgwzl.jpg)

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](https://dev-to-uploads.s3.amazonaws.com/i/9puluy0v7ma77tsu4n37.jpg)

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] (https://docs.atlas.mongodb.com/security-add-mongodb-users/#database-user-privileges) 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](https://code.visualstudio.com/)*

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
```bash
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:
```javascript
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:
```bash
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:
```javascript
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:

```json
"scripts": {
  "dev": "nodemon app.js"
}
```
and now we can run `npm run dev` in the terminal and it should look like this:
![nodemon](https://dev-to-uploads.s3.amazonaws.com/i/v44jmip5nqlsm621dki2.jpg)

In *app.js*.

Remove this:
```javascript
app.get('/', (req, res) => {
    res.send("Hello World!")
})
```

and import our dependencies like this:

```javascript
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](https://dev-to-uploads.s3.amazonaws.com/i/yw6b1nggiwtqupyihtta.jpg)
  
and in the next step click on *Connect your application* and you should see this screen:
![connectionstring](https://dev-to-uploads.s3.amazonaws.com/i/b0mru76us6cn4okxrvx1.jpg)

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*
```javascript
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:

```javascript
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`

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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](https://dev-to-uploads.s3.amazonaws.com/i/vbloksmbmbb3ay8w7vfb.jpg)
 
Now lets check our database to confirm that it's indeed created.
 
![mongo-confirmcreation](https://dev-to-uploads.s3.amazonaws.com/i/7mmoneg9i0l4ge7c8b0g.jpg)
 
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:

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

and in *book.controller.js*

```javascript
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](https://dev-to-uploads.s3.amazonaws.com/i/90xv4mypmex8jeu9kpog.jpg)
 
## Get All Books

Add the route to *book.routes.js*:

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

and in *book.controller.js*

```javascript
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](https://dev-to-uploads.s3.amazonaws.com/i/ibrdv3l64diyejf2vwow.jpg)
 
## Update Book

Add the route to *book.routes.js*:

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

and in *book.controller.js*

```javascript
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](https://dev-to-uploads.s3.amazonaws.com/i/4afr5zvsrda9lpm4ajqi.jpg)

We got one more route and that is:

## Delete Book

Add the route to *book.routes.js*:
```javascript
router.delete("/:id/delete", book_controller.book_delete);
```
and in *book.controller.js*

```javascript
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](https://twitter.com/edindzabija).