How to create simple RESTful API in node.jsπβ
In this blog, we will learn how to build a simple API using ExpressJs and MongoDb with CRUD functions for Users and respective posts.
Introduction
API is an acronym for Application Programming Interface which has became an integral part of Software Development.
RESTful API's are API's that conform the REST architectural style. The acronym for REST is Representational State Transfer which is an architectural style that defines a set of properties and constraints based on HTTP.
We'd be requiring runtime environment and applications:
- NodeJS
- Insomnia
- MongoDB compass
- VScode
So, lets get started without further ado
Create Project File
// Create directory for your new project
mkdir restAPI
// Navigate into the directory
cd restAPI
Initialize NodeJs project with yarn init
.
Next, we'd be installing Express, Nodemon, CORS and Setup Server installation
// Installation of required packages
yarn add express cors bcrypt body-parser dotenv joi mongoose
// Installation of developer dependency
yarn add -D nodemon
It will take a while to install the dependencies considering your internet speed.
Let's write some code. For the IDE I'll go for vs code. Use your preferred IDE to open the project directory and create a file app.js and modify package.json as shown below.
{
"name": "rest-api",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"dev": "nodemon server.js",
"start": "node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.0.1",
"dotenv": "^14.1.0",
"body-parser": "^1.20.0"
"express": "^4.17.2",
"joi": "^17.6.0",
"jsonwebtoken": "^8.5.1",
"mongoose": "^6.2.9",
"cors": "^2.8.5"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}
In this file I have made app.js as the entry file, now we will code the file and setup our server.
Coding
app.js file
Create the app.js file and add the code.
require('dotenv').config();
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const cors = require('cors');
const bodyParser = require('body-parser');
const PORT = process.env.PORT || 3000;
app.use(cors())
app.use(bodyParser.json())
// Import Routes
const postsRoute = require('./routes/posts');
const userRoute = require('./routes/user');
app.use('/posts', postsRoute);
app.use('/user', userRoute);
//Routes
app.get('/', (req, res) => {
res.send('We are on HomePage')
});
// Connect MongoDB
mongoose.connect( process.env.MONGODB,{ useNewUrlParser: true, useUnifiedTopology: true
}).then(()=>{
console.log('Connection Successful');
}).catch((error)=>{
console.log('Something went wrong', error)
});
app.listen(PORT,()=>console.log(`Listening on port ${PORT}`));
Code Explanation
- We are defining the variables and importing the packages.
mongoose.connect()
is used to connected to the MongoDB Compass.app.listen()
is used to start local server at PORT:3000Now run the server by
npm start
and you'll get this.
Create Model Schema for Database
User Schema
./models/User.js
const mongoose = require('mongoose');
const UserSchema = mongoose.Schema({
name: {
type: String,
required: true,
min: 6,
max: 255
},
email: {
type: String,
required: true,
min: 6,
max: 255
},
password: {
type: String,
required: true,
min: 6,
max: 1024
},
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('User', UserSchema);
Code Explanation
- name: Username of the user.
- email: It is used to verify the email using
joi
validation. - password: password for the user.
- date: Automatically, assigned at the time of creation of post.
Post Schema
./models/Post.js
const mongoose = require('mongoose');
const PostSchema = mongoose.Schema({
userID: {
type: String
},
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
- userID: The id of the user who created the post.
- title: Title of the post.
- description: Description for the post.
- date: Automatically, assigned at the time of creation of post.
User Login and Registration Validation
Validation.js
const Joi = require("joi");
const registerValidation = data=>{
const RegisterSchema = Joi.object({
name: Joi.string().min(6).required(),
email: Joi.string().min(6).required(),
password: Joi.string().min(6).required()
});
return RegisterSchema.validate(data);
};
const loginValidation = data=>{
const LoginSchema = Joi.object({
email: Joi.string().min(6).required().email(),
password: Joi.string().min(6).required()
});
return LoginSchema.validate(data);
}
module.exports.loginValidation = loginValidation;
module.exports.registerValidation = registerValidation;
Code Explanation
Joi
allows us to create blueprints of Javascript objects that ensure that we process and ultimately accept accurate data.- After creating the object we are using the
JoiObject.validate(data)
function to validate the our accepted data.
Create Routes to access model and database
./routes/user.js
We will implement the following endpoints
POST /user/register
POST /user/login
DELETE /user/{uid}/only
DELETE /user/{uid}/all
const express = require("express");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken")
const router = express.Router();
//MongoDB Model
const User = require("../models/User")
const Post = require("../models/Post")
//validation import
const { registerValidation, loginValidation } = require("../validation");
//register user
router.post("/register", async(req, res)=>{
//validate user
const { error } = registerValidation(req.body);
if(error){
return res.status(200).send(error.details[0].message);
}
//check if User already in databse
const emailExist = await User.findOne({email: req.body.email});
if(emailExist){
return res.status(400).send("Email already exists");
}
//Hash Password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(req.body.password, salt);
//Validate and create user
const user = new User({
name: req.body.name,
email: req.body.email,
password: hashedPassword
});
try{
const savedUser = await user.save();
res.json({name: user.name, email:user.email});
}
catch(err){
res.json({message:err});
}
});
//Login User
router.post("/login", async(req,res)=>{
const {error} = loginValidation(req.body);
if(error){
return res.status(400).send(error.details[0].message);
}
// check if email exists
const user = await User.findOne({email:req.body.email});
if(!user) return res.status(400).send("Email Does Not Exist");
const validPass = await bcrypt.compare(req.body.password, user.password)
if(!validPass ) return res.status(400).send("Invalid Password");
//Create & assign token
const token = jwt.sign({_id: user_id}, process.env.TOKEN_SECRET);
res.header("auth-token",token).send(token);
});
//Delete user
router.delete('/:uid/only', async(req,res)=>{
try{
const removedUser = await User.remove({_id:req.params.uid});
res.json(removedUser);
}
catch(err){
res.json({message:err});
}
});
router.delete('/:uid/all', async(req, res) => {
try {
const removedUser = await User.remove({ _id: req.params.uid, get: req.body });
const removedPosts = await Post.deleteMany({ userID: req.params.uid });
res.json(removedUser);
res.json(removedPosts);
} catch(err) {
res.json({ message: err });
}
});
module.exports = router;
Code Explanation
- We are creating
POST
method for registration and login requests and validating the information/data. - If any error happens, we are returning the error and messages.
- For the password we are generating 10 rounds of salt and encrypted with that. During login we are encrypting the login password and comparing with the hashed password in the database.
- API to delete the user.
./routes/posts.js
We will implement the following endpoints:
- POST
/posts/{uid}
create new post for the user - GET
/posts/{uid}
get all posts related to the user - GET
/posts/{uid}/{postID}
get specific post related to the user - UPDATE
/posts/{uid}/{postID}
update the post related to that user using uid for validation and postID to update post title - DELETE
/posts/{uid}/{postID}
delete all data regarding the posts about the user
const express = require("express");
const router = express.Router();
const Post = require("../models/Post")
//create a post for the user
router.post('/:uid', async(req,res)=>{
const post = new Post({
userId: req.params.uid,
title: req.body.title,
description: req.body.description
})
try{
const savedPost = await post.save();
res.json(savedPost);
}
catch(err){
res.json{message:err};
}
})
//Get all posts for user
router.get('/:uid', async(req,res)=>{
try{
const posts = await Post.find({ userID:{ $eq: req.params.uid }});
console.log(posts)
res.json(posts);
}
catch(err){
res.json({message: err});
}
})
// get specific posts of user
router.get("/:uid:/postId", async(req,res)=>{
try{
const post =await Post.findById(req.params.postId);
if(post.userID === req.params.uid){
res.json(post);
}
else{
res.json({message: "Incorrect User"})
}
}
catch(err){
res.json({message:err});
}
});
// update specific post of a user
router.update('/:uid/:postId', async(res,req)=>{
try{
const updatedPost = await Post.updateOne({ _id: req.params.postId, userID: {$eq: req.params.uid}},{
$set:{
title: req.body.title
}
});
res.json(updatedPost);
}catch(err){
res.json({message:err});
}
});
// Delete Specific post of a user
router.delete('/:uid/:postId', async(req, res) => {
try {
const removedPost = await Post.remove({ _id: req.params.postId, userID: { $eq: req.params.uid } });
res.json(removedPost);
} catch(err) {
res.json({ message: err });
}
});
module.exports = router;
Points to Remember
- Donβt forget to replace your own MONGODB-KEY in .env file
- Better to create TODO then execute it. For example,