Simplifying Data Validation in Express.js Routes with Class Validator Decorators
Validating user input is a crucial part of building robust and secure web applications. In Express.js, the process of validating incoming data can be simplified and streamlined using the class-validator
library. This article will demonstrate how to use class-validator
in an Express.js application to validate request bodies, URL parameters, and query parameters with ease.
Introduction to Class Validator
class-validator
is a powerful library that provides decorators and validation functions to validate JavaScript objects based on defined constraints. It works seamlessly with TypeScript and provides a declarative way to define validation rules using decorators.
Setting Up the Express.js Application
Before we dive into the validation process, let's set up a basic Express.js application. We'll use the express
and class-validator
packages, so make sure to install them by running the following command:
npm install express class-validator
Now, let's create a new file named app.ts
tsconfig.json
and include the following code:
tsconfig.json
{
"compilerOptions": {
"target": "es2021",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"strictNullChecks": false,
"declaration": true,
"noImplicitAny": false,
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
app.ts
import { IsString, IsNotEmpty, MinLength, ValidationError, validate } from "class-validator";
import Express, { NextFunction, Request, Response } from "express";
const app = Express();
// Express middleware for parsing request bodies
app.use(Express.json());
app.use(Express.urlencoded({ extended: true }));
// Your code goes here
app.listen(1000, () => {
console.log("Server started");
});
In the above code, we import the necessary modules and set up the basic Express.js application. We'll continue writing the validation logic within the provided Your code goes here
section.
Creating a Validation DTO (Data Transfer Object)
To validate the request data, we'll define a validation DTO class using the decorators provided by class-validator
. This DTO class will represent the structure of the data we expect to receive in the request.
In this example, we'll create a CreateUserRequest
class to validate the request body. The CreateUserRequest
class should have the name
and password
properties, each decorated with appropriate validation decorators.
Here's an example implementation of the CreateUserRequest
class:
export class CreateUserRequest {
@IsString()
@IsNotEmpty()
name: string;
@IsString()
@MinLength(6)
@IsNotEmpty()
password: string;
}
In the above code, we use decorators like @IsString()
, @IsNotEmpty()
, and @MinLength()
to define validation rules for the name
and password
properties.
Implementing the Validation Middleware
To integrate the validation logic into our Express.js application, we'll create a validation middleware function. This middleware function will be responsible for validating the request data based on the provided DTO class.
Here's the implementation of the validation middleware:
export function Body<T extends object>(dtoClass: new () => T) {
return validationMiddleware(dtoClass, "body");
}
export function Param<T extends object>(dtoClass: new () => T) {
return validationMiddleware(dtoClass, "params");
}
export function Query<T extends object>(dtoClass: new () => T) {
return validationMiddleware(dtoClass, "query");
}
export function validationMiddleware<T extends object>(
dtoClass: new () => T,
body: string
) {
return async (req: Request, res: Response, next: NextFunction) => {
const dtoInstance = new dtoClass();
const requestBody = {
body: req.body,
query: req.query,
params: req.params,
};
Object.assign(dtoInstance, requestBody[body]);
requestBody[body] = dtoInstance;
const errors: ValidationError[] = await validate(dtoInstance,{
forbidUnknownValues:true,
whitelist: true,
forbidNonWhitelisted: true,
});
if (errors.length > 0) {
const validationErrors = errors.map((error) => ({
[error.property]: Object.values(error.constraints || {}),
}));
const errorMessage = "Invalid data provided";
const errorResponse = { message: errorMessage, errors: validationErrors };
return res.status(422).json(errorResponse);
}
next();
};
}
In the above code, the validationMiddleware
function takes two arguments: dtoClass
(the DTO class to validate against) and body
(the request property to validate, such as "body", "params", or "query"). Inside the middleware function, we create an instance of the DTO class and assign the corresponding request property values to it.
We then use the validate
function from class-validator
to validate the DTO instance against the defined validation rules. If any validation errors occur, we format the errors and send an appropriate error response.
Additionally, we check for unnecessary fields in the request data that are not present in the DTO class. If any such fields are found, we return an error response indicating the presence of unnecessary fields.
Using the Validation Middleware in Routes
Now that we have the validation middleware implemented, we can use it in our Express.js routes. Let's create a route to handle a POST request and validate the request body using the CreateUserRequest
DTO class.
Here's an example implementation of the route:
app.post("", Body(CreateUserRequest), (req, res) => {
console.log(req.body);
return res.send("ok");
});
In the above code, we use the Body
decorator function, which internally calls the validationMiddleware
function, passing the CreateUserRequest
DTO class and the string "body" to specify that we want to validate the request body.
When a POST request is made to this route, the middleware will automatically validate the request body based on the defined validation rules in the CreateUserRequest
class. If the validation passes, the route handler function will be executed.
Full code
import { IsString, IsNotEmpty, MinLength, ValidationError, validate } from "class-validator";
import Express, { NextFunction, Request, Response } from "express";
const app = Express();
export class CreateUserRequest {
@IsString()
@IsNotEmpty()
name: string;
@IsString()
@MinLength(6)
@IsNotEmpty()
password: string;
}
function validateDecorator<T extends object>(dtoClass: new () => T, property: string) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (req: Request, res: Response, next: NextFunction) {
const dtoInstance = new dtoClass();
const requestBody: any = {
body: req.body,
query: req.query,
params: req.params,
};
Object.assign(dtoInstance, requestBody[property]);
requestBody[property] = dtoInstance;
const errors: ValidationError[] = await validate(dtoInstance, {
forbidUnknownValues: true,
whitelist: true,
forbidNonWhitelisted: true,
});
if (errors.length > 0) {
const validationErrors = errors.map((error) => ({
[error.property]: Object.values(error.constraints || {}),
}));
const errorMessage = "Invalid data provided";
const errorResponse = { message: errorMessage, errors: validationErrors };
return res.status(422).json(errorResponse);
}
return originalMethod.call(this, req, res, next);
};
return descriptor;
};
}
app.use(Express.json()) // add body parsing middleware
.use(Express.urlencoded({ extended: true }));
class UserController {
@validateDecorator(CreateUserRequest, "body")
@validateDecorator(CreateUserRequest, "query")
createUser(req: Request, res: Response) {
console.log(req.body);
return res.send("ok");
}
}
const userController = new UserController();
app.post("", userController.createUser.bind(userController));
app.listen(1000, () => {
console.log("server started");
});
Conclusion
In this article, we explored how to simplify data validation in an Express.js application using the class-validator
library. We learned how to define a validation DTO class with decorators, implement a validation middleware function, and utilize the middleware in our Express.js routes.
By leveraging the power of class-validator
, we can ensure that incoming data meets our application's requirements and respond with appropriate error messages when validation fails. This approach helps us build more robust and secure Express.js applications with ease.
Subscribe to my newsletter
Read articles from abdullah ajibade directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by