Claude Skills Guide

Claude Code NestJS Guards Interceptors Pipes Deep Dive

When building robust Node.js applications with NestJS, understanding guards, interceptors, and pipes is essential for creating maintainable and secure code. These three middleware-like components form the backbone of NestJS’s request processing pipeline, each serving a distinct purpose in your application’s lifecycle. This guide walks you through each concept with practical examples you can implement immediately in your projects.

Understanding the NestJS Request Pipeline

Before diving into individual components, it’s crucial to understand how requests flow through a NestJS application. When a client sends a request, it passes through several stages: first, the request hits guards for authorization, then pipes for validation and transformation, and finally, interceptors wrap the entire response process for logging, caching, or modification.

This layered approach allows you to keep your business logic clean by separating cross-cutting concerns from your route handlers. Claude Code can help you generate these components quickly while ensuring they follow NestJS best practices.

Guards: Securing Your Routes

Guards determine whether a request should be handled by a route handler. They return a boolean value—true allows the request to proceed, while false blocks access. Unlike middleware, guards have access to the ExecutionContext, giving them information about the route being accessed.

Creating an Auth Guard

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.replace('Bearer ', '');
    
    if (!token || !this.validateToken(token)) {
      throw new UnauthorizedException('Invalid or missing token');
    }
    
    return true;
  }

  private validateToken(token: string): boolean {
    // Your token validation logic here
    return token.length > 0;
  }
}

Using Guards in Controllers

Apply guards at the controller or method level:

@Controller('users')
@UseGuards(AuthGuard)
export class UsersController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    return { id, name: 'John Doe' };
  }
}

Actionable Advice: Create role-based guards by extending your base guard with role checking. This keeps authorization logic reusable across multiple protected endpoints.

Pipes: Transforming and Validating Data

Pipes operate on method arguments before they reach your route handler. They’re perfect for data validation, type transformation, and parsing input from requests. NestJS provides built-in pipes like ValidationPipe and ParseIntPipe, but you can create custom pipes for specific needs.

Built-in Pipe Examples

@Controller('products')
export class ProductsController {
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    // id is already converted to a number
    return { id, name: 'Product' };
  }

  @Post()
  create(@Body(new ValidationPipe({ whitelist: true })) createProductDto: CreateProductDto) {
    // Only validated properties in createProductDto
    return createProductDto;
  }
}

Creating a Custom Pipe

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class CustomValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    if (metadata.type === 'param' && value < 1) {
      throw new BadRequestException('ID must be greater than 0');
    }
    return value;
  }
}

Actionable Advice: Use ValidationPipe with class-validator decorators for automatic DTO validation. Set whitelist: true to strip unknown properties and prevent over-posting attacks.

Interceptors: Wrapping Request Lifecycle

Interceptors can wrap the method execution before and after the handler runs. They transform the returned value, catch exceptions, extend the basic response handling, and even replace the method execution entirely. Use interceptors for logging, response formatting, caching, and timing metrics.

Building a Logging Interceptor

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const method = request.method;
    const url = request.url;
    const now = Date.now();

    return next
      .handle()
      .pipe(
        tap(() => console.log(`${method} ${url} - ${Date.now() - now}ms`)),
      );
  }
}

Response Formatting Interceptor

@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map(data => ({
        success: true,
        data,
        timestamp: new Date().toISOString(),
      })),
    );
  }
}

Apply interceptors globally, at the controller level, or on specific methods:

// Global
app.useGlobalInterceptors(new LoggingInterceptor());

// Controller or method
@Controller('users')
@UseInterceptors(LoggingInterceptor)
export class UsersController {}

Actionable Advice: Combine interceptors with RxJS operators for powerful patterns. Use retry() for transient failures, timeout() for long-running operations, and catchError() for centralized error handling.

Putting It All Together

The real power of NestJS emerges when you combine these three components strategically. Here’s a typical flow:

  1. Guard checks if the user is authenticated and authorized
  2. Pipe validates and transforms incoming request data
  3. Interceptor logs the request, measures performance, and formats the response

This separation of concerns keeps your code modular and testable. Each component has a single responsibility, making your application easier to maintain and extend.

Best Practices Summary

By mastering guards, interceptors, and pipes, you’ll build NestJS applications that are secure, well-structured, and production-ready. Claude Code can accelerate your learning by generating these patterns while you focus on your business logic.

Built by theluckystrike — More at zovo.one