Skip to content

Custom Validation with class-validator

Sometimes TSOA builtin validation is not enough, in this case we can utilise more powerful libraries like class-validator

This chapter will explain how to use class-validator with TSOA/Express.

The @Middlewares decorator can be used to apply custom middleware to an endpoint in your TypeScript code.

For example, to apply two middlewares pass array of middleware functions to @Middlewares decorator, like this:

ts
@Middlewares([jwt, validateBody(RequestClass)])

Install class-validator package

Install class-validator and class-transformer as it described in their repos

https://github.com/typestack/class-transformer

https://github.com/typestack/class-validator

Write custom middleware

The example class for validation middleware using class-validator:

ts
import { ClassConstructor, plainToInstance } from 'class-transformer';
import { validateSync } from 'class-validator';
import { NextFunction, Request, Response } from 'express';
import { ValidateError } from 'tsoa-next';

export function validateBody<T extends object>(targetClass: ClassConstructor<T>) {
   return async (req: Request, _res: Response, next: NextFunction) => {
      const instance = plainToInstance(targetClass, req.body);
      const errors = validateSync(instance, {
         forbidUnknownValues: true,
         validationError: {
            target: false
         }
      });
      const fieldsErrors: { [name: string]: { message: string; value: string } } = {};

      if (errors.length > 0) {
         errors.forEach(error => {
            if (error.constraints) {
               fieldsErrors[error.property] = {
                  message: Object.values(error.constraints).join(', '),
                  value: error.value
               };
            }
            if (error.children) {
               error.children.forEach(errorNested => {
                  if (errorNested.constraints) {
                     fieldsErrors[errorNested.property] = {
                        message: Object.values(errorNested.constraints!).join(', '),
                        value: errorNested.value
                     };
                  }
               })
            }
         });
         next(new ValidateError(fieldsErrors, 'Validation failed'));
         return;
      }
      next();
   };
}

Annotate class with class validator:

ts
class RequestClass {
   @Length(1, 2000)
   text: string;
}

Usage in controller:

ts
import {
    Controller,
    Middlewares,
    Post,
    Route,
    SuccessResponse,
    Body
} from 'tsoa-next';
import { provide } from 'inversify-binding-decorators';
import {validateBody} from "../middleware/ValidationMiddleware";

@provide(PostController)
@Route('/post')
export class PostController extends Controller {

    @SuccessResponse(200, 'Post created')
    @Post()
    @Middlewares([jwt, validateBody(RequestClass)])
    public async create(
        @Body() request: RequestClass
    ): Promise<void> {
        console.log(`validated request: ${request}`);
    }
}