DEV Community

Cover image for Validating requests using validation pipes in Typescript
Eric O Okiti
Eric O Okiti

Posted on

Validating requests using validation pipes in Typescript

When building APIs, validation of the request body is a must-have. One of the popular tools for validation in Node.js is JOI. I've used Joi for years, and it gets the job done. Recently, I've been exploring other efficient and cleaner ways of archiving this, especially with TS. If you've used Nest JS before, you might have stumbled on validation pipes, enabling you to implement validation using your DTOs and validation decorators. I'll look into how we can archive this in our projects.

Please note that the focus of this post is not folder structure. Instead, it is on how to validate requests. So I'm using a flat folder system in this post. Feel free to structure and nest your files as you please.

We'll start by installing two libraries we will use, class-transformers and class-validator. Class-transformer will transform our class-based dto or types into an object and map them to the request body. Class-validator will make it possible to add validation rules using decorators.

npm i class-validator class-tranformer
Enter fullscreen mode Exit fullscreen mode

An assumption is that you're using express in building your API. Next, we'll create a file called validation.ts

touch validation.ts
Enter fullscreen mode Exit fullscreen mode

Add the following code snippet to the just created file.

import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
export * from 'class-validator';

export const validationPipe = async (schema: new () => {}, requestObject: object) => {
  const transformedClass: any = plainToInstance(schema, requestObject);
  const errors = await validate(transformedClass);
  if (errors.length > 0) {
    return errors;
  }
  return true;
};
Enter fullscreen mode Exit fullscreen mode

From the above snippet, we have a function that takes two parameters, the schema or dto or class-based types and the request object. First, we use plainToInstace to convert and map our dto into an object we can validate. Next, we use the validate function from the class-validator library to validate the request and return the errors, if any.

Simple right? Now we can use this function in our Node.js API server.

...
import { validationPipe, IsString, IsNumber } from './validation';

class bookDto {
  @IsString()
  name: string;
  @IsNumber()
  year: number;
  @IsNumber
  price: number;
}

app.post('/books', async (req, res) => {
  const result = await validationPipe(booksDto, { ...req.body })
  if(result.errors) {
     res.status(400).json({ ...result })
  }
});
Enter fullscreen mode Exit fullscreen mode

This way, we can use our class-based types for validating requests and type checking, making our code cleaner and more maintainable than using a separate schema for validation.

We can take it a step forward and use it as a middleware function.

Using as a middleware

We could use the validation pipe as middleware, meaning cleaner code. To archive this, we'll create a new file named validate.ts or any other name you prefer to use.

touch validate.ts
Enter fullscreen mode Exit fullscreen mode

Add the code snippet below into the file

...
import { validationPipe, IsString, IsNumber } from './validation';

export const validationMiddleware (validationSchema) => async (req, res, next) => {
    const result: any = await validationPipe(validationSchema, { ...req.body, ...req.params }, { pretty: false });
      if (result.errors) {
        console.log(result);
        return res.status(400).json({
          success: false,
          ...result,
        });
      }

      next();
      return true;
}; 
Enter fullscreen mode Exit fullscreen mode

We can now use our newly created middleware in our app.

import { validationMiddleware } from './validate.ts';
import { validationPipe, IsString, IsNumber } from './validation';

class UserDto {
  id: number;
  @IsString()
  firstName: string;
  @IsString()
  @MaxLength(4)
  lastName: string;

  @IsBoolean()
  @IsOptional()
  verified: boolean;
}
...
app.post('/', validationMiddleware(UserDto), (req, res) => {
  ...
  # Your code goes here.
});
Enter fullscreen mode Exit fullscreen mode

I've combined all the code in this post into an npm package called pipe-validation. Like & comment if love the post or think there's any improvement that can be made.

Top comments (0)