← Back to Blog

Building a REST API with NestJS and Prisma: Input Validation & Transformation

Tasin Ishmam
Tasin Ishmam
July 19, 2022
Updated July 8, 2026
8 min read

Input validation in NestJS is handled by the built-in ValidationPipe: you declare rules with class-validator decorators on your DTO classes, register the pipe globally, and every incoming request body is checked before it reaches your route handlers. This tutorial is the second part of a five-part series on building a REST API with NestJS and Prisma ORM. In it, you will add validation and transformation to the Median blog API: reject malformed input, strip unknown fields with the whitelist option, and convert URL path parameters from strings to numbers with ParseIntPipe.

Updated (July 2026): This tutorial has been fully revised for Prisma ORM 7 and NestJS 11, and continues directly from the updated first part of the series. Every command and code block below was run end-to-end against prisma@7.8, @prisma/client@7.8 and @nestjs/core@11, with the database on Prisma Postgres.

Introduction

In the first part of this series, you created a new NestJS project and integrated it with Prisma ORM, PostgreSQL and Swagger. Then, you built a rudimentary REST API for the backend of a blog application called "Median".

In this part, you will learn how to validate the input, so it conforms to your API specifications. Input validation is performed to ensure only properly formed data from the client passes through your API. It is best practice to validate the correctness of any data sent into a web application. This can help prevent malformed data and abuse of your API.

You will also learn how to perform input transformation. Input transformation is a technique that allows you to intercept and transform data sent from the client before being processed by the route handler for that request. This is useful for converting data to appropriate types, applying default values to missing fields, sanitizing input, etc.

Development environment

To follow along with this tutorial, you will be expected to:

  • ... have Node.js (v18 or higher) installed.
  • ... have a PostgreSQL database. The easiest option is Prisma Postgres; you can also run Postgres locally with Docker or a native install.
  • ... have the Prisma VSCode Extension installed. (optional)
  • ... have access to a Unix shell (like the terminal/shell in Linux and macOS) to run the commands provided in this series. (optional)

Note 1: The optional Prisma VSCode extension adds IntelliSense and syntax highlighting for Prisma.

Note 2: If you don't have a Unix shell (for example, you are on a Windows machine), you can still follow along, but the shell commands may need to be modified for your machine.

Set up the project

The starting point for this tutorial is the ending of part one of this series: a rudimentary REST API built with NestJS 11 and Prisma ORM 7, with a working database connection and seed data. If you haven't completed the first tutorial yet, work through it first; it takes about 20 minutes and every step in this article builds directly on it.

Before continuing, make sure the project runs:

  1. Start the development server:
npm run start:dev
  1. Confirm the API documentation is available at http://localhost:3000/api/.

Note: The original edition of this series linked to a companion GitHub repository. That repository still targets the older Prisma 4 and NestJS 8 stack, so for the updated series you should continue from your own project from part one.

Project structure and files

Your project should have the following structure:

median
  ├── node_modules
  ├── generated
  │   └── prisma
  ├── prisma
  │   ├── migrations
  │   ├── schema.prisma
  │   └── seed.ts
  ├── src
  │   ├── app.module.ts
  │   ├── main.ts
  │   ├── articles
  │   └── prisma
  ├── test
  │   ├── app.e2e-spec.ts
  │   └── jest-e2e.json
  ├── README.md
  ├── .env
  ├── eslint.config.mjs
  ├── nest-cli.json
  ├── package-lock.json
  ├── package.json
  ├── prisma.config.ts
  ├── tsconfig.build.json
  └── tsconfig.json

The notable files and directories in this repository are:

  • The src directory contains the source code for the application. There are three modules:
    • The app module is situated in the root of the src directory and is the entry point of the application. It is responsible for starting the web server.
    • The prisma module contains PrismaService, which wraps the Prisma Client, your database query builder.
    • The articles module defines the endpoints for the /articles route and accompanying business logic.
  • The prisma directory has the following:
    • The schema.prisma file defines the database schema.
    • The migrations directory contains the database migration history.
    • The seed.ts file contains a script to seed your development database with dummy data.
  • The generated/prisma directory contains the generated Prisma Client. In Prisma 7, the Client is generated into your project as source files instead of into node_modules.
  • The prisma.config.ts file is Prisma's central configuration file, where the database connection URL and the seed command live.
  • The .env file contains the DATABASE_URL connection string for your database.

Note: For more information about these components, go through part one of this tutorial series.

Perform input validation

To perform input validation, you will be using NestJS Pipes. A pipe in NestJS is a class that validates or transforms the arguments of a route handler before the handler runs. Nest invokes a pipe before the route handler, and the pipe receives the arguments destined for the route handler. Pipes are similar to middleware, but the scope of pipes is limited to processing input arguments. NestJS provides a few pipes out-of-the-box, but you can also create your own custom pipes.

Pipes have two typical use cases:

  • Validation: Evaluate input data and, if valid, pass it through unchanged; otherwise, throw an exception when the data is incorrect.
  • Transformation: Transform input data to the desired form (e.g., from string to integer).

A NestJS validation pipe will check the arguments passed to a route. If the arguments are valid, the pipe will pass the arguments to the route handler without any modification. However, if the arguments violate any of the specified validation rules, the pipe will throw an exception with an HTTP 400 Bad Request status.

The following two diagrams show how a validation pipe works, for an arbitrary /example route.

In this section, you will focus on the validation use case.

Set up ValidationPipe globally

To perform input validation, you will be using the built-in NestJS ValidationPipe. The ValidationPipe provides a convenient approach to enforce validation rules for all incoming client payloads, where the validation rules are declared with decorators from the class-validator package.

To use this feature, you will need to add two packages to your project:

npm install class-validator class-transformer

The class-validator package provides decorators for validating input data, and the class-transformer package converts incoming JSON into typed instances of your DTO classes so those rules can run. Both packages are required by the ValidationPipe.

Now import the ValidationPipe in your main.ts file and use the app.useGlobalPipes method to make it available globally in your application:

// src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(new ValidationPipe());

  const config = new DocumentBuilder()
    .setTitle('Median')
    .setDescription('The Median API description')
    .setVersion('0.1')
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

Add validation rules to CreateArticleDto

You will now use the class-validator package to add validation decorators to CreateArticleDto. You will apply the following rules to CreateArticleDto:

  1. title can't be empty or shorter than 5 characters.
  2. description has to have a maximum length of 300.
  3. body and description can't be empty.
  4. title, description and body must be of type string and published must be of type boolean.

Open the src/articles/dto/create-article.dto.ts file and replace its contents with the following:

// src/articles/dto/create-article.dto.ts

import { ApiProperty } from '@nestjs/swagger';
import {
  IsBoolean,
  IsNotEmpty,
  IsOptional,
  IsString,
  MaxLength,
  MinLength,
} from 'class-validator';

export class CreateArticleDto {
  @IsString()
  @IsNotEmpty()
  @MinLength(5)
  @ApiProperty()
  title: string;

  @IsString()
  @IsOptional()
  @IsNotEmpty()
  @MaxLength(300)
  @ApiProperty({ required: false })
  description?: string;

  @IsString()
  @IsNotEmpty()
  @ApiProperty()
  body: string;

  @IsBoolean()
  @IsOptional()
  @ApiProperty({ required: false, default: false })
  published?: boolean = false;
}

These rules will be picked up by the ValidationPipe and applied automatically to your route handlers. One of the advantages of using decorators for validation is that the CreateArticleDto remains the single source of truth for all arguments to the POST /articles endpoint. So you don't need to define a separate validation class.

Test out the validation rules you have in place. Try creating an article using the POST /articles endpoint with a very short placeholder title like this:

{
  "title": "Temp",
  "description": "Learn about input validation",
  "body": "Input validation is...",
  "published": false
}

You should get an HTTP 400 error response along with details in the response body about what validation rule was broken:

{
  "message": ["title must be longer than or equal to 5 characters"],
  "error": "Bad Request",
  "statusCode": 400
}

This diagram explains what the ValidationPipe is doing under the hood for invalid inputs to the /articles route:

Input validation flow with ValidationPipe

Strip unnecessary properties from client requests

The CreateArticleDto defines the properties that need to be sent to the POST /articles endpoint to create a new article. UpdateArticleDto does the same, but for the PATCH /articles/{id} endpoint.

Currently, for both of these endpoints it is possible to send additional properties that are not defined in the DTO. This can lead to unforeseen bugs or security issues. For example, you could manually pass invalid createdAt and updatedAt values to the POST /articles endpoint. Since TypeScript type information is not available at run-time, your application will not be able to identify that these fields are not available in the DTO.

To give an example, try sending the following request to the POST /articles endpoint:

{
  "title": "example-title",
  "description": "example-description",
  "body": "example-body",
  "published": true,
  "createdAt": "2010-06-08T18:20:29.309Z",
  "updatedAt": "2021-06-02T18:20:29.310Z"
}

In this way, you can inject invalid values. Here you have created an article that has an updatedAt value that precedes createdAt, which does not make sense.

To prevent this, you will need to filter any unnecessary fields/properties from client requests. Fortunately, NestJS provides an out-of-the-box option for this as well. All you need to do is pass the whitelist: true option when initializing the ValidationPipe inside your application.

// src/main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));

  // ...
}
bootstrap();

With this option set to true, ValidationPipe will automatically remove all non-whitelisted properties, where "non-whitelisted" means properties without any validation decorators. It's important to note that this option will filter all properties without validation decorators, even if they are defined in the DTO.

Now, any additional fields/properties that are passed to the request will be stripped automatically by NestJS, preventing the previously shown exploit.

Note: The NestJS ValidationPipe is highly configurable. All configuration options available are documented in the NestJS docs. If necessary, you can also build custom validation pipes for your application.

Transform dynamic URL paths with ParseIntPipe

Inside your API, you are currently accepting the id parameter for the GET /articles/{id}, PATCH /articles/{id} and DELETE /articles/{id} endpoints as a part of the path. NestJS parses the id parameter as a string from the URL path. Then, the string is cast to a number inside your application code before being passed to the ArticlesService. For example, take a look at the DELETE /articles/{id} route handler:

// src/articles/articles.controller.ts

@Delete(':id')
@ApiOkResponse({ type: ArticleEntity })
remove(@Param('id') id: string) {   // id is parsed as a string
  return this.articlesService.remove(+id); // id is converted to number using the expression '+id'
}

Since id is defined as a string type, the Swagger API also documents this argument as a string in the generated API documentation. This is unintuitive and incorrect.

Instead of doing this transformation manually inside the route handler, you can use a NestJS pipe to convert id to a number automatically. Add the built-in ParseIntPipe to the controller route handlers for these three endpoints:

// src/articles/articles.controller.ts

import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
  ParseIntPipe,
} from '@nestjs/common';

export class ArticlesController {
  // ...

  @Get(':id')
  @ApiOkResponse({ type: ArticleEntity })
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.articlesService.findOne(id);
  }

  @Patch(':id')
  @ApiOkResponse({ type: ArticleEntity })
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() updateArticleDto: UpdateArticleDto,
  ) {
    return this.articlesService.update(id, updateArticleDto);
  }

  @Delete(':id')
  @ApiOkResponse({ type: ArticleEntity })
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.articlesService.remove(id);
  }
}

The ParseIntPipe will intercept the id parameter of string type and automatically parse it to a number before passing it to the appropriate route handler. This also has the advantage of documenting the id parameter correctly as a number inside Swagger. If a client sends a value that can't be parsed to a number, like GET /articles/abc, the pipe rejects the request with an HTTP 400 response:

{
  "message": "Validation failed (numeric string is expected)",
  "error": "Bad Request",
  "statusCode": 400
}

Frequently asked questions

Summary and final remarks

Congratulations! In this tutorial, you took an existing REST API built with NestJS 11 and Prisma ORM 7 and:

  • Integrated validation using the ValidationPipe.
  • Stripped client requests of unnecessary properties with the whitelist option.
  • Integrated ParseIntPipe to parse a string path variable and convert it to a number.

You might have noticed that NestJS heavily relies on decorators. This is a very intentional design choice. NestJS aims to improve code readability and modularity by heavily leveraging decorators for various kinds of cross-cutting concerns. As a result, controllers and service methods do not need to be bloated with boilerplate code for doing things like validation, caching, logging, etc.

In the next part of this series, you will learn how to handle errors in a NestJS and Prisma application, including the database errors that validation alone can't catch. If you want to go deeper on the Prisma side first, the Prisma getting started guide and Prisma Migrate docs are good next steps, and Prisma Postgres is an easy way to stand up the database layer for follow-on work.

Looking ahead: Prisma Next is a TypeScript-native rewrite of Prisma ORM, built for AI coding agents and currently in early access. It becomes Prisma 8 at general availability; until then, Prisma 7 stays the production choice. To try it, run npm create prisma@next or read the early access docs.

Build your next app with Prisma

Start free. Scale when you’re ready.

Try Prisma
Share this article