Validating POST Requests in Express with Joi and TypeScript

Validating POST Requests in Express with Joi and TypeScript

Validating user input is a critical part of building a reliable and secure web application. In this article, we will focus on validating POST requests in Express using Joi, a powerful schema description language and data validator for JavaScript and TypeScript.

Getting Started

To use Joi with TypeScript and Express, you need to install the necessary packages. Run the following commands to install them:

npm install express joi
npm install -D @types/express @types/joi

Defining the Joi Schema

The first step to validating a POST request is to define a schema for the request body. This schema should define the required fields and their respective types. For example, if we are building a login route, we might need email and password fields. Create a file - validateLoginRequests.ts

import Joi from "joi";

const loginSchema = Joi.object({
  email: Joi.string()
    .email()
    .custom((value, helpers) => {
      if (!value.endsWith("@gmail.com")) {
        return helpers.error("any.invalid");
      }
      return value;
    }, "Email Validation")
    .required(),
  password: Joi.string().required(),
});

For the email field, it uses the email() method to ensure that the value is a valid email address. Then, it adds a custom validation rule with the custom() method. This custom rule checks if the email address ends with '@gmail.com'. If the email does not end with '@gmail.com', it returns an error. The second parameter to the custom() method, "Email Validation", is a label for this custom rule.

For the password field, the schema simply requires it to be a string.

This schema can be used to validate user input in a login form, ensuring that the user has provided a Gmail address and a password.

Creating Validation Middleware

Next, we create a middleware function that validates the request body against the schema before it reaches our route handler. This function takes the Joi schema as an argument and returns an Express middleware function. In validateLoginRequests.ts ,

import { Request, Response, NextFunction } from 'express';

export const validateLoginRequests = (
  req: Request,
  res: Response,
  next: NextFunction
) => {
    try{
        const { error } = loginSchema.validate(req.body);
        if (error) {
          res
            .status(400)
            .send({ message: "Invalid Request!", error: error.details[0].message });
          return;
        } else {
          next();
        }
    }catch(error) {
        next(error);
    }
};

The function validateLoginRequests takes three arguments: req, res, and next. These are the request, response, and next middleware function in the Express middleware pipeline.

The function starts by trying to validate the request body (req.body) against the loginSchema defined elsewhere. The validate() function returns an object that includes an error property if the validation failed.

If the error property is present, the function responds with a 400 status code and sends a JSON response with a message "Invalid Request!" and the details of the validation error.

If there is no validation error, the function calls the next() function to pass control to the next middleware function in the pipeline.

If any other error occurs during the execution of the function (for example, if loginSchema is not defined), the function catches the error and passes it to the next middleware function for error handling.

Using Validation Middleware in Routes

Finally, we use the validation middleware in our route definition before the route handler. In the route_file_path, for example loginRoute.ts

import { Router } from 'express'; // import the Router class from the express module

import { login } from '../controller/authController';

import {validateLoginRequests} from '_path_to_the_validateLoginRequests_middleware';

const router = Router(); // Create an instance of the Router

router.post('/login', validateLoginRequests, login); // handle POST requests to '/login' by calling the login function from the authController

export default router; // export the router instance as the default export of this module

The Router class from the Express module is imported. An instance of this class is created using the Router() function. This router object is used to define routes in the application.

The login function from the 'authController' module and the validateLoginRequests middleware function from another module are imported. These functions will be used to handle and validate login requests.

The router.post() function is used to define a route for handling POST requests at the '/login' endpoint. The first argument to this function is the path of the route. The second argument is the middleware function validateLoginRequests that will be called when a POST request is made to the '/login' endpoint. This middleware function validates the request before it reaches the route handler. The third argument is the route handler function login that will be called if the request passes the validation.

Finally, the router instance is exported as the default export of this module. This means that it can be imported in other modules using the import statement.

Conclusion

Joi is a powerful tool for validating user input in Express applications. With its rich set of validation rules and flexibility, it can help you to ensure that your application receives the right kind of data and behaves predictably.