Creating your first API setup using Nodejs


Using Nodejs for Rest APIs
Using Nodejs for Rest APIs


Sample application is already there

Node-backend on github is there having all the code except csv file for data.

CSV file you can download from here


A little word

I will keep it simple. This post is for you if

  • You have started learning nodejs backend already and been adding everything into just one server.js,
  • You have no idea about folder structure of Production Level Nodejs app,
  • You are making a Nodejs backend for your app which is going to scale up very soon

The problem I am trying to state

In you have been learning Nodejs and making a backend from the same, probably you are adding everything inside server.js. If not organised properly, you can get easily entangled into multiple routes and CRUD(Create,Read, Update, Delete) operations.

Here is an example what your server.js looks like when it is not organised:

///////////////////////////////// // SERVER.JS ///////////////////////////////// var express = require('express'), app2 = require('./app'), app = express(), port = process.env.PORT || 8000, cors = require('cors'), mongodb = require("mongodb"), bodyParser = require('body-parser'), http = require("http"), serveStatic = require('serve-static'), router = express.Router(); const path = require('path'); const MongoClient = require('mongodb').MongoClient; var ObjectID = mongodb.ObjectID; var db; var book_collection = "books"; app.use(cors()); //=====================LOCAl API======================================== app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); var MONGODB_URI= 'mongodb://localhost:27017/bookstore'; mongodb.MongoClient.connect(MONGODB_URI, function(err, database) { if (err) { console.log("Connecting DB failed coz\n\n" + err); process.exit(1); } db = database; console.log("Database connection ready at " + db); }); function handleError(res, reason, message, code) { console.log("\n\n\nERROR: " + reason); res.status(code || 500).json({ "\n\n\n\nerror ": message }); } app.get("/api/books", function(req, res) { db.collection(book_collection).find({}).toArray(function(err, docs) { if (err) { handleError(res, err.message, "Failed to get Books."); } else { res.status(200).json(docs); } }); }); app.post("/api/books", function(req, res) { var newBook = req.body; if (!req.body.name) { handleError(res, "Invalid user input", "Must provide a Book.", 400); } db.collection(book_collection).insertOne(newBook, function(err, doc) { if (err) { handleError(res, err.message, "Failed to create new Book."); } else { res.status(201).json(doc.ops[0]); } }); }); app.get("/api/books/:id", function(req, res) { db.collection(book_collection).findOne({ _id: new ObjectID(req.params.id) }, function(err, doc) { if (err) { handleError(res, err.message, "Failed to get book"); } else { res.status(200).json(doc); } }); }); app.put("/api/books/:id", function(req, res) { var updateDoc = req.body; delete updateDoc._id; db.collection(book_collection).updateOne({ _id: new ObjectID(req.params.id) }, updateDoc, function(err, doc) { if (err) { handleError(res, err.message, "Failed to update book"); } else { updateDoc._id = req.params.id; res.status(200).json(updateDoc); } }); }); app.delete("/api/books/:id", function(req, res) { db.collection(book_collection).deleteOne({ _id: new ObjectID(req.params.id) }, function(err, result) { if (err) { handleError(res, err.message, "Failed to delete book"); } else { res.status(200).json(req.params.id); } }); }); app.use(express.static(__dirname + '/dist')); app.use('/css', express.static(__dirname + '/src/assets/homepage/css')); app.use('/js', express.static(__dirname + '/src/assets/homepage/js')); app.use('/img', express.static(__dirname + '/src/assets/homepage/img')); app.get('/*', function(req, res) { res.sendFile(path.join(__dirname + '/src/index.html')); }); console.log("\n\npath is " + __dirname + "\n\n"); app.listen(port); app.use(function(req, res) { if (res.status(200)) { console.log('Hello Response'); } res.status(404).send({ url: req.originalUrl + ' not found' }); // }); var date = new Date(); console.log('Server running at ' + port + ' at time: ' + date);

This file above has verbs for one, just ONE collection called BOOKS.

Imagine if we add same more verbs (get, post, delete) for another collection called USERS. Doesn’t looks good right?

This tutorial make you use of module.exports or Export feature of ES6 to organise and structure files. Routes are created into separate files, and respective functions which pulls data from database are written separately.

I will be using MongoDB here,with a collection books (table is called Collection in MongoDB) with a few document (row/record is called doument).

For your convenience, I am adding this CSV file which you can import in MongoDB when installation is complete.

1. Code Setup

First, your folder structure for this setup should look like this image. Do create a folder called NodeApp, and add folder as follow:

Best practise to keep everything seprate
Best practise to keep everything seprate

More about these in upcoming sections.

Next, open terminal in root directory of your project folder NodeApp and type npm init.Follow through on screen instruction, fill name of Author or project if you want to name it as yours.

Just say yes to everything in CMD
Just say yes to everything in CMD

This will install some basic dependencies in folder named node_modules and one special file named Package.json.

Package.json contains dependencies and their respective versions in it as JSON format. If you ever have to deploy this project into server, server will look into Package.json files and will install same dependencies onto Server so that you don’t have to go and type “npm install” for each and every dependency. Sounds cool?

Next, Lets install the dependencies which are actually required for running app.

npm install --save mongoose express body-parser cors

Install Expressjs to leverage Nodejs features. Also, mongodb is needed to interact with MongoDB instance, mongoose is a library for Object data modelling when interacting with Database which we will explain later, body-parser to parse incoming request coming as json or url-encoded or sometimes raw.

2. Setting up Server.js

This is the most sophisticated part. In essence, this is starting point of your backend. We will add code into this file in multiple steps with explanation.

With all Basic and Required dependencies set,in root of your project, create one Server.js.

This is the one file which will intercept URL you accessed using your Frontend code and then acts accordingly whether it is a Add Record or Delete record request coming from your Frontend.

First, import dependencies into it. Paste following code:

var express = require("express"), app = express(), port = process.env.PORT || 8000, cors = require("cors"), mongodb = require("mongodb"), bodyParser = require("body-parser");
  • Express, to use express framework and utilize Nodejs underlying features.
  • App is the instance of express required to create a server
  • Port, is the port on which backend will run. process.env.PORT is useful when you have a environment.ts file having a variable called Port. Else, you can hardcode your choice of port number inot it, just like 8000 here.
  • CORS, is to allow Cross origin resource sharing. Due to security constraints, servers allow specific applications or links like abc.com to access the APIs, while blocking all others so that its API are not consumed/manipulated by other websites.

Since we are in development mode and not making any production level project, we can add CORS and learn about whitelisting the websites later on.

  • bodyParser, is a tool to parse upcoming request from Frontend. Request may come in any forms; json, url-encoded, raw etc. The use of this tool is to accept and process json request before our Handler/Controllers starts working on it. Hence, your request which is coming from Frontend is converted to json format and then processed into req.body inside books.controller.js

Time to check if this our server.js will run properly. Append following this same file:

app.listen(port); app.use(function(req, res) { if (res.status(200)) { console.log("Server working"); } res.status(404).send({ url: req.originalUrl + " not found" }); }); console.log("Server running at " + port + " at time: " + date);

This is to run your app on port mentioned just above.

Open terminal from root of project and type node server.js.

As expected, connection established
As expected, connection established

Yeah, your code runs.

3. Setting MongoDB connection

Now your server.js is ready to connect to Database but where is your database?

Time to create database connection. We can fetch data from our Book collection from database we craeted in step 1.

Create a file called dbconnection.js and keep it in root of your project. We need to keep our MongoDB instance URl seprate in this file to keep things organised and not putting load on Server.js for database interactions. Add username and password in MongoDB endpoint, if you have setup them in MongoDB app.

Import your installed mongoose here as your first line in dbconnection.js. Mongoose will leverage use of native MongoDB features.

var mongoose = require("mongoose");

This is how you define you connection endpoint to your running databse, in next line:

var MONGODB_URI = "mongodb://localhost:27017/someDatabase"; var _db, _dbError; _db = mongoose.connection;

Following code initiate connection to mongodb and throws error if your MongoDB database is down or if any other error occurs.

mongoose.connect(MONGODB_URI, (err, database) => { if (err) { _dbError = err.message; return console.log("\nConnection error \n\n" + err) }; console.log("\n\nConnection Successful."); });

Try one thing here. Don’t run your MongoDB instance (the databse actually) and try to run your bakend using node server.js. You will see something like this:

As expected, connection attempt is failed
As expected, connection attempt is failed

ooh, that failed. Why? Because your app is not able to find running database locallty on your system.

When everything is complete, Don’t forget to export _db in last line, as you will need it in Server.js:

module.exports = _db;

dbconnection.js is complete now.

4. Your first Route

Route is an endpoint which decides that what should happen when you access specific route with or without additional parameters. For ex: if ‘/book’ is you route in server.js, then the ‘book.route.js’ file will have the specified method in it to get all books from your running MongoDB.

Basically we are redirecting routes to CRUD verbs here. CRUD i.e Create, Read, Update and Delete verbs are just synonym of operations in this case.

Lets write some code. wihtin folder called “routes” create filed called book.route.js.

Copy the code and paste into file:

const express = require('express'), app = express(), router = express.Router(), // levraging Router from Express module book_controller = require('../controllers/books.controller'); // connecting Books controller to Books route //*************** verbs for books collection *************** router.get("/api/books", book_controller.get_books); //getting all books at once router.get("/api/books/:id",book_controller.get_books_byId);//get one book by specifying id router.post("/api/books/",book_controller.insertBook);//Adding a new book router.delete("/api/books/:id",book_controller.deleteBookbyId);//Deleting book by id module.exports = router;

This code states that when you will try to /api/bestseller_books , it will execute the get_books method in books.controller which we are about to write in next step.

5. Your first Handler/Controller

Handler or Controller is something which translates routes into an executable script. In simple words, if you access one route, Controller will execute the respective function which you mentioned in route file. Lets make one.

Create a file books.controller.js inside controllers folder your created in step 1.

Before writing controllers, do get familiar with the choice of database. This is the file which contains query to get data from database. So, if you are using MongoDB, make sure you know bit about getting data from collections, removing data from collections and other queries as well.

Also, since we are querying data, make sure there is a folder called models in root of your project and contains Models defined in it. This part is called Object data modelling (ODM).

When we are working on modern javascript technologies like Angular, React or any other frameworks, Object data modelling is much needed is more clear depiction of what resides in database.

Now go inside models folder and add following code into your first model called books.model.js:


const mongoose = require('mongoose'); const BookSchema = new mongoose.Schema({ amtsave: { type:Number, required:true }, brand: String, breadcrumbs:String, country:String, desc:String, discount:String, domain:String, image:String, insertedon: String, list_price:String, model:String, name:String }); module.exports = mongoose.model('books', BookSchema);

That one is just for Book collection. If you have more collections like Users, bestseller_books etc, you have to create seprate models for those as well.

What we have done here is written data in terms of javascript objects. The advantage of this over “select * from books” is that programmer starts thinking in terms of javascript and not in SQL. Here, you can easily identify what data will reside in object for Books. Also, we have used mongoose here which is a MongoDB object modelling tool available to make the ODM part easier.

Next, add following lines into books.controller.js which we created earlier:

var express = require("express"), app = express(), mongodb = require("mongodb"); var ObjectID = mongodb.ObjectID; const Book = require('../models/books.model'); var bestseller_books = "bestseller_books"; exports.get_books = function(req, res) { const db = req.app.db; db.collection(bestseller_books).find({}).toArray(function(err, docs) { if (err) { handleError(res, err.message, "Failed to get Books."); } else { res.status(200).json(docs); } }); }; exports.get_books_byId = function(req, res) { const db = req.app.db; db.collection(bestseller_books).findOne({ _id: new ObjectID(req.params.id) }, function(err, doc) { if (err) { handleError(res, err.message, "Failed to get book"); } else { res.status(200).json(doc); } } ); }; exports.insertBook = function(req, res) { const db = req.app.db; var newBook = req.body; if (!req.body.name) { handleError(res, "Invalid user input", "Must provide a Book.", 400); } db.collection(bestseller_books).insertOne(newBook, function(err, doc) { if (err) { handleError(res, err.message, "Failed to create new Book."); } else { console.log("....Added a new book"); res.status(201).json(doc.ops[0]); } }); } exports.updateBookbyId = function(req, res) { var updateDoc = req.body; delete updateDoc._id; db.collection(bestseller_books).updateOne({ _id: new ObjectID(req.params.id) }, updateDoc, function(err, doc) { if (err) { handleError(res, err.message, "Failed to update book"); } else { updateDoc._id = req.params.id; res.status(200).json(updateDoc); } } ); } exports.deleteBookbyId = function(req, res) { const db = req.app.db; db.collection(bestseller_books).deleteOne({ _id: new ObjectID(req.params.id) }, function(err, result) { if (err) { handleError(res, err.message, "Failed to delete book"); } else { res.status(200).json(req.params.id); } } ); } function handleError(res, reason, message, code) { console.log("\n\n\nERROR: " + reason); res.status(code || 500).json({ "\n\n\n\nerror": message }); }

We have imported books.model to use Book model as instance. Also, within method get_books(), you will see code written which is as same as MongoDB syntax to find all records from collections. Learn about find query using MongoDB.

mongodb.ObjectID is used when we are editing a particular record in collections, usually by id.

var bestseller_books = "bestseller_books"; is the name of your collection within your Database.

6. Revisiting Server.js

For one last time we will make changes in Server.js. This is just for adding you route and dbconnection.js.

Also we have to add your routes into Server.js, so that server will know which routes or endpoints are there to be accessed. If related route is not found, code to throw and show error will be executed which we will add in third step of this section.

Copy and paste this code, right below dependencies you just imported:

var main = require('./routes/routes'); var books = require('./routes/books.route'); var cart = require('./routes/cart.route'); var Users = require('./routes/users.route');

This is the route you created for books, you remember? How it will get used is in next line. Copy following and add below your route import:

app.get('/', main); app.get('/api/bestseller_books', books); app.get('/api/bestseller_books/:id', books); app.post('/api/bestseller_books', books);

Makes Sense?

Here, for every route ending with “/bestseller_books”, we are redirecting it to Books controller. There according to verb written in server.js, respective code will get executed. If it is “app.post”, then insertBook method will be executed, if it is just “app.get” then get_books() will be fired.

With all done, again run “node server.js” and try to access “localhost:8000/api/bestseller_books” from your browser. This by default, will fire app.get method/verb and will executed “get_books()” from books controller.

JSOn being rendered
JSOn being rendered

If you can see the books which were entered in MongoDB on CSV import, you did good job.

Checkout my project Node-Backend here on github

Thanks for reading.

Sudheer — listens to SoundCloud, Strings being his favourite band, learns stuff to make life easier.