Restful API with Node.js, Express, and MongoDB
In this tutorial, we learn about how to set up a simple restful API with Node.js, Express, and MongoDB.
Step 1: Install Node and NPM
Before we start, we need to have NPM and Node.js installed on our machine. We relying on NPM Node Package Manager to manage all the packages and modules for Node.js.
Run the command below to verify you have NPM and Node installed. Make sure you see the version of node and npm return on command prompt.
node -vnpm -v
Step 2: Create Node.js App
Once we have the npm installed, setting up a Node.js app can be very fast. Simply open a command prompt and run the commands below.
mkdir nodejs-api-tutorial // Create a project root directory
cd nodejd-api-tutorial
Next, we need to create a package.json file in the root folder. This file holds the metadata and project dependencies. Run the command as following and go through the steps.
npm init
Step 3: Install Dependencies
Node.js has many modules to provide the functionality needed to build an API application. Run the command below to install the modules using NPM. It is quick to build a restful API with Node.js using NPM.
npm install express body-parser cors helmet mongoose app-config --save
- Express is a web application framework.
- Mongoose provides a straight-forward, schema-based solution to model your application data.
- Helmet helps you secure your Express.js apps by setting various HTTP headers.
- body-parser extract the entire body portion of an incoming request stream and exposes it on req.body
- CORS allows or restricts requested resources on a web server depend on the HTTP request origin.
- app-config helps you manage app configuration across multiple environments.
Your package.json should look like this:
Step 4: Setup server.js Entry Point.
Copy the code below to your server.js file.
const express = require('express');
const bodyParser = require('body-parser');
const config = require('app-config');
const cors = require("cors");
const helmet = require("helmet");const app = express();
const port = 5000;// Optional: Configure cors to prevent unauthorised domain to access your resources
const allowlist = ['http://yourallowdomain.com'];
const corsOptionsDelegate = (req, callback) => {
let corsOptions; let isDomainAllowed = allowlist.indexOf(req.header('Origin')) !== -1;
if (isDomainAllowed) {
// Enable CORS for this request
corsOptions = { origin: true }
} else {
// Disable CORS for this request
corsOptions = { origin: false }
}
callback(null, corsOptions)
}app.use(cors(corsOptionsDelegate));// Optional: To add additional security to protect your HTTP headers in response.
app.use(helmet());// parse requests of content-type - application/json
app.use(bodyParser.json());// parse requests of content-type - application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }));app.use('/', (req, res) => res.send('Hello World SGWebFreelancer'));// Any url that doesn't match will return as a 404
app.use(function(req, res, next) {
const err = new Error('Not Found');
res.status(404).send('Service Not Found 404');
err.status = 404;
next(err);
});const server = app.listen(port, () => console.log('Server is up and running at port: ' + port));
To test the code, run the command to start the server.
npm start // this command will run node server.js
Access http://localhost:5000 on your browser.
Step 5: Create MongoDB Connection
In this tutorial, we will be relying on the Mongoose module to insert and query our application data. It’s good practice to store our application and DB configuration variables in a dedicated file based on the environment. In this case, create a folder called config, and inside the directory, create another folder called dev. Before that, ensure you have MongoDB installed and started in your local machine.
Subsequently, create a db.js file to store the DB connection variables.
const DBPORT = '27017';
const DBNAME = 'dbtutorial';
const DBUSER = 'user';
const DBPWD = 'password';
const DBHOST = '127.0.0.1';
const uri = 'mongodb://'+ DBUSER+':'+DBPWD+'@'+DBHOST+':'+DBPORT + '/'+DBNAME;module.exports = {
'url' : uri
};
Update the server.js file to connect the DB.
...
// import the mongoose library
const mongoose = require('mongoose');
...// DB connection
mongoose.connect(config.db.url, // refer to the config/dev/db.js file
{
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true
}).then(()=> console.log('DB connected successfully.')
).catch(err => console.log('DB connection failed ' + err)
);
Now, update the startup command to set the environment to dev, so that the app-config knows which environment configuration file should be read.
...
"scripts": {
"start": "NODE_ENV=dev node server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
...
Step 6: Create Model and Controller
In this tutorial, we will use Model-View-Controller (or MVC) architectures to build the API. But since is a restful API, we will omit the view in this tutorial.
Create a models/product.js file. With Mongoose, we can map the application data with the MongoDB Schema easily.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;productSchema = new Schema({
name: String,
description: String,
price: Number
},
{
timestamps: true // automatically manage createdAt and updatedAt properties
},
{
collection: 'Product'
});const Product = mongoose.model('Products', productSchema);
const Models = { Product: Product };
module.exports = Models;
Create a controllers/product.js file to handle the HTTP request.
const Product = require('../models/product').Product;const products = { // GET get all Products
getProducts: function(req, res) {
Product.find({}).then(data => {
res.send(data);
}).catch(err => {
res.status(500).send({
message: err.message || "Error occurred while retrieving products."
});
})
},
// GET Product by id
getProductsByID: function(req, res) {
const id = req.params.id;
Product.findById(id).then(data => {
if (!data)
res.status(404).send({
message: "Product with id " + id + " is not found."
});
else res.send(data);
}).catch(err => {
res.status(500).send({
message: err.message || "Error occured while retrieving Product with id " + id
})
})
},
// POST add new Product
addProduct: function(req, res) {
if (!req.body.name) {
res.status(400).send({
message: "Product name can not be empty!"
});
return;
} const product = new Product({
name: req.body.name,
description: req.body.description,
price: req.body.price
}) product.save().then(data => {
res.send(data);
}).catch(err => {
res.status(500).send({
message: err.message || "Error occurred while creating the Product."
});
})
},
// PUT update Product by id
updateProduct: function(req, res) {
if (!req.body) {
return res.status(400).send({
message: "Data to update can not be empty!"
});
} const id = req.params.id; Product.findByIdAndUpdate(id, req.body, {
useFindAndModify: false
})
.then(data => {
if (!data) {
res.status(404).send({
message: `Failed to update Product with id=${id}.`
});
} else res.send({
message: "Product was updated successfully."
});
})
.catch(err => {
res.status(500).send({
message: "Error occured while updating Product with id=" + id
});
});
},
// DELETE delete Product by id
deleteProduct: function(req, res) {
const id = req.params.id; Product.deleteOne({
_id: id
})
.then(data => {
if (!data) {
res.status(404).send({
message: `Failed to delete Product with id=${id}.`
});
} else res.send({
message: "Product was deleted successfully."
});
})
.catch(err => {
res.status(500).send({
message: "Error occured while deleting Product with id=" + id
});
});
},
// DELETE remove all Products
deleteAllProducts: function(req, res) {
Product.deleteMany({})
.then(data => {
res.send({
message: "All Products was deleted successfully."
});
})
.catch(err => {
res.status(500).send({
message: "Error occured while deleting all Products"
});
});
},
}module.exports = products;
Step 7: Create Routes
Create a routes/index.js file to map the HTTP request URL to the controller.
const express = require('express');
const router = new express.Router();
const product = require('../app/controllers/product.js')router.get(`/api/products`, [product.getProducts]);
router.get(`/api/products/:id`, [product.getProductsByID]);
router.post(`/api/products`, [product.addProduct]);
router.put(`/api/products/:id`, [product.updateProduct]);
router.delete(`/api/products/:id`, [product.deleteProduct]);
router.delete(`/api/products`, [product.deleteAllProducts]);module.exports = router;
Then update the server.js to register the routes.
...
//app.use('/', (req, res) => res.send('Hello World SGWebFreelancer'));app.use('/', require('./routes'));
Now, restart the server and test the API.
Additional
Window Environment
If you are using Windows, the command should look like this “set NODE_ENV=dev && node server.js”.
MongoDB
Run the command below to check if you have MongoDB installed in your local machine. If not, you can download it from here.
mongod --version
Run the command below to enter MongoDB Shell and create a database.
mongo> use dbtutorial // create db schema dbtutorial> db.createUser({user: "user", pwd: "password", roles:[]}) // create user> show users // to check user added successfully or not.
Nodemon
Nodemon is a tool that helps develop node.js applications by automatically restarting the node application when file changes in the directory are detected. This library is useful when doing development.
npm install nodemon --save"scripts": {
"start:dev": "NODE_ENV=dev nodemon server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
Docker Compose
To run the system in production, it is easier to deploy using Docker.
Create Dockerfile
# pull the Node.js Docker image
FROM node:alpine# create the directory inside the container
WORKDIR /usr/src/app# copy the package.json files from local machine to the workdir in container
COPY package*.json ./# run npm install in our local machine
RUN npm install# copy the generated modules and all other files to the container
COPY . .# our app is running on port 5000 within the container, so need to expose it
EXPOSE 5000# the command that starts our app
CMD ["node", "server.js"]
Create nginx/default.conf file. Use the nginx as proxy server to route the incoming request to the proper port.
server {
listen 80;
server_name apiserver; location / {
proxy_pass http://apiserver:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Create nginx/Dockerfile file
FROM nginx
COPY default.conf /etc/nginx/conf.d/default.conf
Create the docker-compose.yml file.
version: "3.3"
services:
apiserver:
build:
context: ./
ports:
- "5000:5000"
nginx:
restart: always
build:
context: ./nginx
ports:
- "80:80"
Then to start the restful API, run the command below:
docker-compose up --build
If you are interested to know more about the docker-compose, you can read the previous post.
Done! You can get the full source code.
If you like the tutorial
