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
- Insomnia
- MongoDB - Create a free account (no installation required)
- Visual Studio Code 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 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.
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:
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
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
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:
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
and in the next step click on Connect your application and you should see this screen:
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 anddb_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:
Now lets check our database to confirm that it's indeed created.
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 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:
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
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.
Top comments (7)
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.
Add the line
to book.model.js
I fixed it above, sorry for that.
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!Just as a remark: You don't need bodyparser as a dependency, it' build into express: app.use(express.json());
It's fixed, thanks.
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!
Awesome mongoose just makes the whole process super simple.