Building a Blogging API with NodeJs/Express

Introduction

In this article, I will be going through a detailed implementation of a blogging application (API) using NodeJs and the Express framework.

The application would have basic read/write functionality, like the ability to create a blog post, edit or even delete blog posts. Registered users would be able to create drafts, which they can publish when they want to and also have access to a list of their blog posts (drafts and published blogs). Visitors using the app should be able to see a list of blogs published by users, and blogs should be searchable by author name, title and tags. We would also implement a feature to be able to order blogs by reading time, read count and timestamp.

Why NodeJs?

NodeJs is an opensource multi-platform JavaScript runtime environment for building fast, scalable web applications (server-side). It is asynchronous and non-blocking in nature which can improve efficiency and throughput. NodeJs uses JavaScript which makes it pretty easy to learn if you’re already familiar with building web applications using JavaScript.

Requirement

  • An Integrated Development Environment (IDE) example

  • Solid foundation and understanding of javascript

  • Package Manager (npm)

Getting Started

We start by initializing a new node project in our working directory with the command:

$ npm init -y

This creates a package.json file which has the metadata for our project. We can then proceed to edit the file and make relevant changes such as the name of the project, author, license if any etc.

We’re using the MVC design pattern so we’ll go ahead and create folders to hold the necessary files.

We first create a source folder /src and inside the folder we create folders for Models, Views, Controllers. We want our code to be modular and neat so we also create another folder for routes and database.

Inside src we create a .gitignore file and inside that file, we include the folders to be ignored if we’re uploading the project to github. We don’t want to upload our node_modules folder and our environment variables so we include those inside .gitignore

/node_modules
.env

Setting Up the Database

We’re using a nosql database (mongodb) for this project so we go ahead and install a library called mongoose that would help in setting up the connection. Inside the terminal we use the command

$ npm install mongoose --save

Earlier we created a folder inside /src for database. Inside the database folder i.e /src/database, we create a new file and name it db.js

We edit db.js and require mongoose, then we create and export a function which will handle connection to mongodb. The function takes in a url parameter which will be the connection string to mongodb

const mongoose = require(‘mongoose’);

module.exports = (url) => {
    mongoose.connect(url || 'mongodb://localhost:27017');

    mongoose.connection.on("connected", () => {
        console.log("successfully connected to database");
    });

    mongoose.connection.on("error", (error) => {
        console.log("There was a problem connecting to mongoDB");
        console.log(error);
    });
}

The first line of our function calls the connect method on mongoose and we use the url parameter as the connection string. On the next line, we use an event listener and pass in a callback to handle the connection event (when connected to mongodb). inside the callback, we simply log the message “successfully connected to database”.

In order to prevent our application from breaking, we catch any error that might occur while trying to connect to mongodb. Mongoose provides another event listener which we use to achieved that. The event listener takes a callback in which we can decide to log the error message.

Defining Models

We navigate to the models folder inside /src and inside that folder we create two files namely:

  • userModel.js

  • blogModel.js

In creating the user Model we first of all import Schema and model from mongoose. We achieve this with destructuring

const { Schema, model } = require(‘mongoose’);

We then create a new objectId from mongoose Schema

const objectId = Schema.objectId

We create a user schema which maps to a MongoDB collection and defines the shape of the documents within that collection

const userSchema = new Schema({
  id: ObjectId,
    first_name: {
        type: String,
        required: true   
    },
    last_name: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true,
        unique: true
    },
    password: {
        required: true,
        type: String
    },
    blogs: [
        {
            type: Schema.Types.ObjectId,
            ref: 'Blog'
        }
    ]
})

In the user schema we define the parameters we want saved as user details. For this project, I’ve chosen the following fields: first_name, last_name, email and password.

We also want blogs created by a user to be linked to that user, and since a user can have more than one blog post, we save the user’s blog(s) in an array. For each item in that array, an objectId from another collection ‘Blog’ is saved and thus we have

blogs: [
        {
            type: Schema.Types.ObjectId,
            ref: 'Blog'
        }
    ]

Following standard practice, we want passwords saved to the database to be encrypted and to achieve that, another library called bcrypt is used. We install bcrypt with a package manager (npm in this case)

$ npm install bcrypt --save

and then require bcrypt inside auth.js

const bcrypt = require(‘bcrypt’);

We then use a pre-save hook on the user schema and pass in a callback. Inside the callback function we use bcrypt to hash user password before saving to the database.

To have access to the user model we define a constant user which is assigned the user model using this keyword. The next line of code prevents passwords from rehashing every time changes are made to the database.

We then await bcrypt to hash the password using 10 salt rounds and the hash value is saved to the password field

userSchema.pre(‘save’, async function (next){
  const user = this;
  if (!user.isModified('password')){
    return next()
  }

  const hash = await bcrypt.hash(this.password, 10)
  this.password = hash;
  next();
});

To prevent certain fields being returned any time the database gets queried, we set the condition on the user schema

userSchema.set(‘toJSON’, {
  transform: (document, returnedObject) => {
    returnedObject.id = returnedObject._id.toString()
    delete returnedObject._id
    delete returnedObject.__v
    delete returnedObject.password
  }
})

We can exclude (delete) as many fields as we want by simply calling the property on returnedObject.

Finally, we create the user model by calling the model we destructured from mongoose earlier and pass in two arguments: the name of the collection and the user schema. We then export the model to be used throughout the application

const User = model(“User”, userSchema);

module.exports = User;

Next we define our blog model inside blogModel.js and require Schema and model just like with the user model

const {Schema, model} = require(‘mongoose’);

We then define an ObjectId from Schema and define our blog Schema. Just as with the userSchema where we use an ObjectId referenced to the blog collection, in the blog schema we also use an ObjectId referenced to the user collection in saving to the author field.

const ObjectId = Schema.ObjectId;

const blogSchema = new Schema({
  id: ObjectId,
  title: {
    type: String,
    required: true,
    unique: true
  },  
  description: String,
  author: {
    type: Schema.Types.ObjectId,
    ref: 'User'
  },      
  state: {
    type: String,
    enum: ['draft', 'published'],
    default: 'draft'
  },
  read_count: {
    type: Number,
    default: 0
  },
  reading_time: {
    type: String
  },
  tags: [String],
  body: {
    type: String,
    required: true
  },
  createdAt: {
    type: Date,
    default: Date.now()
  },
  updatedAt: {
    type: Date,
    default: Date.now()
  },
  publishedAt: {
    type: Date,
    default: ''
  }  

})

To update the timestamp field for updatedAt, here we can use a pre-save event listener so that whenever changes are made to the database, the current time would be saved

blogSchema.pre(‘save’, function(next) {
  this.updatedAt = Date.now();
  return next();
})

To exclude some fields in the returned object on every database query, we call the set method on blogSchema

blogSchema.set(‘toJSON’, {
  transform: (document, returnedObject) => {
    returnedObject.id = returnedObject._id.toString()
    delete returnedObject._id
    delete returnedObject.__v
  }
})

We then create the blog model and export it to be used throughout the entire application

const Blog = model(‘Blog’, blogSchema);

module.exports = Blog;

Authentication

For this project, token based authentication is used. We install jsonwebtoken as a dependency using the command

$ npm install jsonwebtoken --save

To implement the authentication strategy, we create another folder inside /src and name it middleware. Inside the middleware folder, i.e /src/middleware, we create a new file named auth.js and begin to edit the file.

The first step is to require jsonwebtoken and the user model. We assign the constant jwt to jsonwebtoken as written below

const jwt = require(‘jsonwebtoken’);
const User = require('../models/userModel');

Since we would be performing time consuming operations like database query, we create an async function in order to control code execution synchronously. We can as well export the function straight away

module.exports = async function (req, res, next){

}

We also use try and catch inside the function for error handling to prevent the application from breaking in the event of an error. In the case of token expiry, we properly define the error message. We destructure TokenExpiredError object from jwt and set a condition to check for any instance of such error. The appropriate response is sent to the client.

The default error message would be to set a status code of 401 to represent ‘unauthorized’ and we send a response with the message “unathorized! invalid token” as a json object

module.exports = async function (req, res, next){
  try {

  }

  catch (error){
    const { TokenExpiredError } = jwt
    if (error instanceof TokenExpiredError){
      return res.status(401).json({message: "Unathorized! Access token expired"})
    }  

    res.status(401).json({message: "Unathorized! invalid token"})
  }
}

Inside the try block, we get the token being sent by the client from the authorization headers by calling a substring() method and passing in a value of 7. we then use jwt to verify the token by calling verify() method and passing the token and a secret.

try {
  const authHeader = req.headers.authorization;

  const token = authHeader.substring(7)
  const authorizeToken = jwt.verify(token, process.env.SECRET)

}

We want to save user information into the request object for future use-case, so we import/require the user model and call mongodb query method find() passing in an objectId. This objectId is a property of the returned object from jwt verifying bearer token.

If no token is sent, we return an error message to the client else we save the user information to the request object

try {
  const authHeader = req.headers.authorization;

  const token = authHeader.substring(7)
  const authorizeToken = jwt.verify(token, process.env.SECRET)

  const user = await User.findById(authorizeToken.id)

  if (!token){
    return res.status(401).json({error: "invalid/no token provided"})
  }

  req.user = user;

  next();    

}

I have chosen to add another level of authentication for modifying user blogs. The strategy here is whenever a user is tries to modify a blog in the database, the id being sent as a request parameter will be cross-checked with the user’s information stored in the database. The operation will only be valid if the user has a blog with a matching id as that in the request parameter.

This file userAuth.js is created also in src/middleware.js and we import the blog model

const Blog = require('../models/blogModel');

A try and catch block is used here also for error handling. In the try block we use the id from request parameters to query the Blog collection, an if condition is used to handle the case in which an invalid/non existent id is sent in the request. For a valid blog id, the blog with such id should be returned from the database query. The returned data should have a field ‘author’ which is an ObjectId. This id is compared to the user id saved in the user property of the request object. A conversion is to Hexadecimal string is made as the data type returned from database for id is an ObjectId

try {
  const { blogId } = req.params;
  const blog = await Blog.findById(blogId)

  if (!blog){
    return res.status(404).json({
      status: false,
      message: "blog not found"
    })
  }        

  const stringIds = [blog.author, req.user._id].map(item => item.toHexString())
  if (stringIds[0] !== stringIds[1]){
    return res.status(422).json({message: "invalid operation"})
  }
  else if (stringIds[0] === stringIds[1]){
    next()
  }

}
catch (error){
  next(error)
}

Controllers

This is where the request handlers would be defined and then exported. Two files are created inside /src/controllers namely: userController.js and blogController.js

Request handler functions for handling user registration and login would be define inside userController. To register a new user, the user model is required and define a function registerUser that takes in a request and a response

const User = require(‘../models/userModel’);


exports.registerUser = function (req, res, next){

}

The user information is gotten from the request body and this information is saved to the database. A status code 201 is sent back to the client signifying that the entry was created successfully as well as a success message and user details in a json object

exports.registerUser = function (req, res, next){
  try {
        const userData = req.body;
        const user = new User(userData);

        await user.save()

        res.status(201).json({
            message: "user profile created successfully", 
            user: user
        })

  }
}

In the catch block we check for an instance of error code 11000. This code signifies the entry already exists in the database. In such a case, a message is sent back to the client indicating that the user already exists. The default is to pass the error to the error handler

catch (error){
  if (error.code === 11000) {
    res.status(400).json({
      status: false,
      message: "user already exists"
    })
  }
  next(error);
}

Next a loginUser function is defined. For this function, jwt would be required to sign the user information and bcrypt to decrypt user password

const jwt = require(‘jsonwebtoken’);
const bcrypt = require('bcrypt');

exports.loginUser = function (req, res, next){

}

The login information from a login form is gotten from the request body and the we query the database to find a user with that information. If the user is found, the password submitted is then compared to the one saved in the database. In a situation where no user is found or the password does not match, we return a message to the client indicating an invalid username or password.

If user is found in the database and password matches, we then use jwt to sign the user information using a secret and an option for expiration is set to 60 minutes. The token is sent to the client

try {
  const {email, password} = req.body
  const user = await User.findOne({ email })
  const userMatch = user === null ? false : await bcrypt.compare(password, user.password)

  if (!(user && userMatch)) {
    return res.status(401).json({
      message: "invalid username or password"
    })
  }

  const token = jwt.sign(
    {
      email: user.email,
      id: user._id
    },
    process.env.SECRET,
    {
      expiresIn: 60 * 60
    }
  )

  return res.status(200).json({message: "Login successful", token });

 }
 catch (error){
   next(error)
 }

Blog Controllers

Creating a Blog Post

The first handler function defined would enable users to create a blog saved as draft. The user model and blog model is required here as they would be used in reading from and writing to the database

const Blog = require(‘../models/blogModel’);
const User = require('../models/userModel');

exports.createDraft = function (req, res, next){

}

We get blog fields such as title, description, tags, and body from the request body. We want blogs to support multiple tags so we split tags by commas and then trim white spaces from the beginning of each tag. An array is created from calling the split method on tags, which is what we want saved to the database.

To implement reading time, we split the body of the article by white spaces which creates a new array, the length of that array is taken as the number of words in the article. The average reading time for most people is between 200 to 250 words per minutes, so we can divide the number of words by any of this values and this gives us the reading time in minutes.

The user id has been saved to the request object earlier in the user authentication, so we save this information as the author of the blog or article

try {
   const {
     title,
     description,
     tags,
     body
   } = req.body

   let tagsArr = tags.split(',')
   tagsArr = tagsArr.map(item => item.trim())

   const reading_time = body.split(' ').length / 200 +' ' + 'mins'
   const userId = req.user._id

   const blog = new Blog({
     title,
     description,
     tags: tagsArr,
     body,
     reading_time,
     author: userId       
   })

   const savedDraft = await blog.save();

   const userInDB = await User.findById(userId);
   userInDB.blogs = userInDB.blogs.concat(savedDraft._id);

   await userInDB.save();

   return res.status(201).json({status: true, blog});
 }
 catch (error){
   next(error)
 }

Next we define a function to enable users publish their blog. To achieve this we simply use the id of the blog to query the database and then update the state of the blog from ‘draft’ to ‘published’. The timestamp for ‘publishedAt’ is also set to the current date and time

exports.publish = async (req, res, next) => {
  try {
    const {blogId} = req.params
    const draft = await Blog.findById(blogId)

    draft.state = 'published';
    draft.publishedAt = Date.now()

    await draft.save()

    return res.json({status: true, blog: draft})
  }
  catch (error){
    next(error)
  }
}

Updating a blog

To update a blog, we take the blog Id and query the database. If the blog is not found, we return a message to the client indicating the blog does not exist in the database, otherwise we update the fields as provided and save to the database -kind of similar to creating a new entry to the database.

A pre-save hook used in the blog model updates the timestamp for ‘updatedAt’

exports.updateBlog = async (req, res, next) => {
  try {
        const { blogId } = req.params;

        const {title, description, tags, body} = req.body;

        const blog = await Blog.findById(blogId)

        if (!blog){
            return res.status(404).json({
                status: false,
                message: "blog not found"
            })
        }

        if (title){
            blog.title = title
        }

        if (description){
            blog.description = description
        }

        if (tags){
            let tagsArr = tags.split(',')
            tagsArr = tagsArr.map(item => item.trim())
            blog.tags = tagsArr
        }
        if (body){
            blog.body = body
        }

        await blog.save()
        return res.json({status: true, blog})
    }
    catch (error){
        next(error);
    }
}

Deleting a blog

To delete a blog, we simply pass in the id of the blog and use the deleteOne method provided by mongodb

exports.deleteBlog = async (req, res, next) => {
  try {
        const { blogId } = req.params;

        const blog = await Blog.deleteOne({_id: blogId})

        return res.json({status: true, blog})
    }
    catch (error){
        next(error);
    }
}

Getting a Blog

To get a blog post, we query the database using the blog Id. If the blog with the specified Id is not found, the appropriate message is sent back to the client with a 404 status code. If the blog is found, we check the state of the blog to ensure it is in the published state and if true, the read count is incremented by 1. So every time we query the database, the read count increments by 1. We then send the blog as a response to the client

exports.getPost = async (req, res, next) => {
  try {

        const { blogId } = req.params

        const blog = await Blog.findById({_id: blogId}, '-createdAt').populate('author', {first_name: 1, last_name: 1})

        if (!blog){
            return res.status(404).json({status: false, message: "blog does not exist"});
        }

        blog.state === 'published' ? blog.read_count++ : false

        await blog.save()

        return res.json({status: true, blog})

    }
    catch (error){
        next(error);
    }

}

Getting User’s blogs

To enable a user get a list of their blogs, we define a handler function to that effect.

exports.userBlogs = async (req, res, next) => {

}

This function is expected to allow user query (filter by state), sorting and pagination. The first step is to define values we would expect from user request. We get the user’s Id from the user information saved in the request object and the remaining values from request query property. Default values are set for page number, limit, the order in which items are to be sorted and a reference field to order the items by

try {
  const id = req.user._id
  const { 
    state, 
    page = 1, 
    limit = 20,
    order = 'asc',
    order_by = 'createdAt'
  } = req.query    
}

We then define two objects to store the user query. In each object, the key would be the field we wish to query and we assign it a value based on the user input, i.e query sent from the client. An if condition is used to define those values

const findQuery = {};

if (state){
 findQuery.state = state
}

For the sort queries, since we would likely expect more than one value — the order to sort items and the field to order the items by — those values are saved into an array. We then iterate through the array and set a condition to set the value of ‘order_by’ attribute in the sort query object

const sortQuery = {};
const sortAttributes = order_by.split(‘,’);

for (let attribute of sortAttributes){
  if (order === 'asc' && order_by){
    sortQuery[attribute] = 1
  }
  if (order === 'desc' && order_by){
    sortQuery[attribute] = -1
  }

}

We can now pass in these values into the blog model’s query methods and return the appropriate query to the client

const userBlogs = await Blog.find({…findQuery, author: id})
.sort(sortQuery)
.limit(limit * 1)
.skip((page -1) * limit)

const count = userBlogs.length;

return res.status(200).json({
  status: true, 
  blogs: userBlogs,
  totalPages: Math.ceil(count / limit),
  currentPage: page
});

All the procedures stated above are defined in the try block. In the catch block, we pass in any error to the error handler.

Getting All Published Blogs

The handler function to get all blogs that have been published would be very similar to the userBlog function, except this time we’re only querying for blogs with the state set as ‘published’. We also add more query parameters like being able to query by the author’s name, the title of the blog, or even tags.

exports.getBlogPosts = async (req, res, next) => {
  try {

    const { 
      author,
      title,
      tags,
      page = 1, 
      limit = 20,
      order = 'asc',
      order_by = 'publishedAt'
    } = req.query;

    const findQuery = {}
    const sortQuery = {}
}

In the findQuery object, we set attributes for author, title and tags using if condition. To set attribute for author, we create a regular expression pattern for the first name and last name, making each pattern case insensitive. We then use both patterns to query the user collection in our database, the resulting object is a list of user information matching the pattern. An array is created from the resulting object using just the Id field, this array is then used to query the blogs collection using the $in operator

if (author) {

  const [first_name, last_name] = author.split(‘ ‘);
  const pattern1 = new RegExp(first_name, 'i')
  const pattern2 = new RegExp(last_name, 'i')
  const blogMatches = await User.find({first_name: pattern1, last_name: pattern2}, '-password')
  const matchArr = blogMatches.map(blog => blog._id)
  findQuery.author = {$in : matchArr}

 }

For title and tags, we simply create a new regular expression pattern that is case insensitive and assign that to their respective attributes. We can now use the findQuery object to query the database where the state of the blog is ‘published’.

Model.find() takes an object with several attributes and since find query is already an object, we iterate through its attributes using a spread operator and then pass in the second option for state.

if (title){
  const titlePattern = new RegExp(title, ‘i’);
  findQuery.title = titlePattern;
}
if (tags){
  const tagsPattern = new RegExp(tags, 'i');
  findQuery.tags = tagsPattern;
}

const sortAttributes = order_by.split(',');

for (let attribute of sortAttributes){

  if (order === 'asc' && order_by){
    sortQuery[attribute] = 1
  }
  if (order === 'desc' && order_by){
    sortQuery[attribute] = -1
  }

}

const blogs = await Blog.find({…findQuery, state: 'published'})
 .populate('author', {first_name: 1, last_name: 1})
 .sort(sortQuery)
 .limit(limit * 1)
 .skip((page - 1) * limit)
const count = blogs.length;

return res.status(200).json({
 status: true, 
 blogs: blogs,
 totalPages: Math.ceil(count / limit),
 currentPage: page
});

Implementing sort query is similar as with the userBlogs function.

Defining Routes

Now that request handlers have been defined, we can define the different routes for user requests with the appropriate handler.

I have created two separate files in src/routes, one for users and the other for the blog. Inside the user route, we import express and the user controller where functions related to handling user registration and login was defined. We create a router from express and for each route we call the function for handling user registration and login

const express = require(‘express’);
const userController = require('../controllers/userController');

const userRouter = express.Router();

userRouter.post('/signup', userController.registerUser);

userRouter.post('/login', userController.loginUser);

module.exports = userRouter;

In the blog route, we import express, the blog controller, and the authentication middlewares. We create a router from express, define route CRUD operations with specified routes and then pass in the validation middlewares before the handler functions or controller.

const express = require(‘express’);
const blogController = require('../controllers/blogController');
const authenticate = require('../middleware/auth');
const authenticateUser = require('../middleware/userAuth');

const blogRouter = express.Router();

blogRouter.get('/', blogController.getBlogPosts)

blogRouter.get('/:blogId', blogController.getPost);

blogRouter.get('/a/userblogs', authenticate, blogController.userBlogs);

blogRouter.post('/save-draft', authenticate, blogController.createDraft);

blogRouter.patch('/p/:blogId', authenticate, authenticateUser, blogController.publish);

blogRouter.patch('/u/:blogId', authenticate, authenticateUser, blogController.updateBlog);

blogRouter.delete('/d/:blogId', authenticate, authenticateUser, blogController.deleteBlog);

module.exports = blogRouter;

Creating the App

A few other dependencies are installed for the app such as express-rate-limit for limiting the number of request a single user can send per time, helmet a nodejs module for securing http headers, and dotenv for access to environment variables.

npm install dotenv helmet express-rate-limit

These packages are required/imported into the app file together with the routers. The app is created using express and then we define the middlewares

const express = require(‘express’);
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');
const userRoute = require('./routes/users');
const blogRoute = require('./routes/blogs');
const limterConfig = require('./config/limiter');
require('dotenv').config();

const app = express();

app.use(express.urlencoded({extended: false}));
app.use(express.json());

const limiter = rateLimit(limterConfig)

app.use(limiter);

app.use(helmet());

Configuration for express-rate-limit is defined inside a separate file in src/config

module.exports = {
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false
}

The routes are then defined and we use a wild card selection for non existent routes

app.use(‘/users’, userRoute);
app.use('/blog', blogRoute);

app.get('/', (req, res) => {
return res.json({ status: true })
})

app.use('*', (req, res) => {
return res.status(404).json({message: "route not found"})
})

A middleware for error handling is also defined, then we connect to the database and listen on the configured port number

app.use((error, req, res, next) => {
  console.log(error)
  const errorStatus = error.status || 500
  res.status(errorStatus).send(error.message)
  next()
}) 

const PORT = process.env.PORT || 3334
const DB_URL = process.env.DB_URL

connectDB(DB_URL);

app.listen(PORT, () => {
    console.log(`Server is listening on port ${PORT}`)
})

User Input Validation

To prevent unwanted entries into the database, we create a validation middleware with the help of another package called joi

npm install @hapi/joi

I have created two files for the validation middlewares in src/validators.

For the user validation middleware, we define a user validation schema and then create a function that takes the user payload from the request body and validates it before passing unto the next middleware

const Joi = require(‘@hapi/joi’);

UserCreateSchema = Joi.object({
  first_name: Joi.string()
    .max(255)
    .trim()
    .required(),
  last_name: Joi.string()
    .max(255)
    .required()
    .trim(),
  email: Joi.string()
    .email()
    .min(5)
    .max(50)
    .required(),
  password: Joi.string()
    .pattern(new RegExp('^[a-zA-Z0–9]{3,30}$'))
    .min(6)
    .max(50)
    .required()
})

async function CreateUserValidationMW (req, res, next){
  const userPayLoad = req.body
  try {
    await UserCreateSchema.validateAsync(userPayLoad)
    next()
  }
  catch (error){
    next({
      message: error.details[0].message,
      status: 400
    })
  }
}

module.exports = { CreateUserValidationMW }

In the user route defined earlier, we then import the validation middleware and implement it

const express = require(‘express’);
const userController = require('../controllers/userController');
const { CreateUserValidationMW } = require('../validators/user.validator');

const userRouter = express.Router();

userRouter.post('/signup', CreateUserValidationMW, userController.registerUser);

userRouter.post('/login', userController.loginUser);

module.exports = userRouter;

A similar process is implemented for blogs validation, but this time we create two validation middlewares: one for creating blogs and the other for updating blogs

const BlogUpdateSchema = Joi.object({
  title: Joi.string()
    .min(5)
    .max(255)
    .trim()
    .optional(),
  description: Joi.string()
    .min(5)
    .max(500)
    .optional()
    .trim(),
  tags: Joi.string()
    .min(5)
    .max(500)
    .optional(),
  body: Joi.string()
    .min(10)
    .trim()
    .optional(),
  state: Joi.string()
    .valid('published')
    .optional()
})
async function UpdateBlogValidationMW (req, res, next){
  const blogPayLoad = req.body
  try {
    await BlogUpdateSchema.validateAsync(blogPayLoad)
    next()
  }
  catch (error){
    next({
      message: error.details[0].message,
      status: 400
    })
  }
}

module.exports = { 
    CreateBlogValidationMW, 
    UpdateBlogValidationMW 
}

The middlewares are imported into the blogs route and implemented there also

const express = require(‘express’);
const blogController = require('../controllers/blogController');
const authenticate = require('../middleware/auth');
const authenticateUser = require('../middleware/userAuth');
const { CreateBlogValidationMW, UpdateBlogValidationMW } = require('../validators/blog.validator');

const blogRouter = express.Router();

blogRouter.get('/', blogController.getBlogPosts)

blogRouter.get('/:blogId', blogController.getPost);

blogRouter.get('/a/userblogs', authenticate, blogController.userBlogs);

blogRouter.post('/save-draft',CreateBlogValidationMW, authenticate, blogController.createDraft);

blogRouter.patch('/p/:blogId',UpdateBlogValidationMW, authenticate, authenticateUser, blogController.publish);

blogRouter.patch('/u/:blogId', UpdateBlogValidationMW, authenticate, authenticateUser, blogController.updateBlog);

blogRouter.delete('/d/:blogId', authenticate, authenticateUser, blogController.deleteBlog);

module.exports = blogRouter;

Thanks for reading.

Here's a Link to the repository