🛡️Validation, Error Handling & Pipes

Add safety and reliability using validation, pipes, and custom error handling mechanisms.

✅ Data Validation with Pipes

Welcome to the chapter on Data Validation with Pipes! In this section, we'll explore how to effectively use class-validator and class-transformer to validate and transform incoming request data in your NestJS applications. We'll cover everything from using built-in pipes to creating custom ones, and discuss best practices for handling validation errors.

💡 Introduction to Pipes

In NestJS, a pipe is a middleware-like feature that allows you to manipulate the request data before it reaches your controller. Pipes can be used for tasks like validation, sanitization, and transformation of incoming data.

  • Pipes are executed in the middleware pipeline after routing
  • They can transform or modify the request body or parameters
  • Validation errors can be caught using pipes and handled gracefully

💡 Understanding Built-In Pipes

NestJS comes with several built-in pipes that you can use out of the box. One of the most commonly used is the ValidationPipe, which is designed to validate request data against a set of defined constraints.

import { ValidationPipe } from '@nestjs/common';

// Global validation configuration
app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
    validateCustomDecorators: true,
  })
);

Using DTOs for Validation

Data Transfer Objects (DTOs) are a great way to define the structure of your request data. By using class-validator decorators, you can easily validate incoming data against these DTOs.

@nestjs/common
import { IsString, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @MinLength(5)
  username: string;

  @IsString()
  password: string;
}

Creating Custom Pipes

If the built-in pipes don't meet your needs, you can easily create custom pipes. This allows you to define specific validation logic that's unique to your application.

import { PipeTransform } from '@nestjs/common';

export class CustomValidationPipe implements PipeTransform {
  transform(value: any) {
    // Add custom validation logic here
    if (value.somethingInvalid) {
      throw new Error('Invalid data');
    }
    return value;
  }
}

💡 Applying Pipes Globally or Per Route

Pipes can be applied in two ways: globally (applies to all routes) or per route (only applies to specific endpoints). Global pipes are useful for common validations, while route-specific pipes allow for more granular control.

  • Global pipes are configured using useGlobalPipes()
  • Route-specific pipes are added using the @UsePipes decorator
// Global pipe configuration
app.useGlobalPipes(new ValidationPipe());

// Route-specific pipe usage
@Post()
@UsePipes(new CustomValidationPipe())
create(@Body() createUserDto: CreateUserDto) {
  // ...
}

Avoiding Common Pitfalls

One of the biggest mistakes developers make is skipping validation for external or user-provided data. Always ensure that any data coming into your application is properly validated to prevent security issues and unexpected behavior.

  • Never skip validation for sensitive data
  • Always whitelist fields in ValidationPipe
  • Validate both request body and query parameters

💡 Best Practices for Data Validation

  • Use DTOs to define your data structure and validation rules
  • Keep your pipes simple and focused on a single responsibility
  • Handle validation errors gracefully using exceptions filters
  • Regularly update your validation logic to match changing requirements

💡 Real-World Applications of Pipes

Pipes are invaluable in real-world applications for tasks like sanitizing user input, validating API requests, and transforming data into a format that your business logic can easily work with. By properly implementing pipes, you can ensure that your application is both robust and maintainable.

🚨 Global Exception Filters & Custom Errors

Welcome to the chapter on Global Exception Filters & Custom Errors! Handling errors is a critical part of building robust applications. In this section, we'll explore how to manage exceptions in your NestJS application using global and custom exception filters.

💡 What are Exception Filters?

An exception filter is a mechanism that intercepts exceptions thrown within your application and provides a standardized way to handle them. NestJS provides both built-in filters and the ability to create custom ones.

  • Global exception filters apply to all routes and controllers.
  • Scoped filters are applied only to specific controllers or methods.
  • Custom exception filters allow you to define how exceptions are handled and what response is sent back to the client.

💡 Key Concepts

  • @Catch() decorator: Specifies which exceptions the filter should catch.
  • HttpException: Built-in exception class for handling HTTP errors with specific status codes.
  • ExceptionFilter interface: Defines the structure for custom filters.

Global Exception Filters

Global exception filters are registered at the application level and apply to all routes. They provide a centralized way to handle errors across your entire application.

import { Catch, ExceptionFilter } from '@nestjs/common';

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  catch(exception: unknown) {
    console.error('Global exception caught:', exception);
    // Handle the exception and send a response
  }
}

// Register the filter in your main.ts
app.useGlobalFilters(new GlobalExceptionFilter());

Custom Exception Filters

You can create custom filters to handle specific types of exceptions. This is useful for providing meaningful error messages and maintaining consistent API responses.

import { Catch, ArgumentsHost } from '@nestjs/common';

@Catch(ForbiddenException)
export class ForbiddenFilter {
  catch(exception: ForbiddenException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    
    response.status(403).json({
      statusCode: 403,
      message: 'Forbidden resource',
    });
  }
}

💡 Best Practices for Exception Handling

  • Always use specific exception types to ensure filters catch the right errors.
  • Avoid exposing internal error details to clients (use custom error messages).
  • Use global filters for common exceptions and scoped filters for specific cases.
  • Log all exceptions in a centralized way for debugging purposes.

Real-World Application

Imagine you're building an e-commerce platform. You could use exception filters to handle scenarios like: - Invalid payment methods - Out-of-stock items - Unauthorized access attempts Each of these scenarios would have its own custom filter and response.

What Not to Do

  • Don't catch exceptions at the controller level if you can handle them globally.
  • Avoid using a single global filter for all exceptions without proper categorization.
  • Never expose raw error stacks to clients.

Quiz

Question 1 of 10

What is the primary purpose of a pipe in NestJS?

  • To validate incoming request data
  • To transform outgoing response data
  • Both A and B
  • None of the above