NestJS — Enterprise Node.js

Custom Decorators

17 min Lesson 16 of 48

Custom Decorators

You have used many decorators — @Get(), @Body(), @UseGuards(). NestJS lets you build your own, making controllers cleaner and intent clearer. This lesson covers parameter decorators, metadata decorators, and combining them — and ties together the whole request lifecycle.

Custom parameter decorators

Reaching into the request for the same value in every handler is repetitive. A custom parameter decorator extracts it once, declaratively. The classic example is a @User() decorator that pulls the authenticated user off the request:

import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const User = createParamDecorator( (data: string, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); return data ? request.user?.[data] : request.user; }, );

Now any handler reads the user with a clean, self-describing parameter:

@Get('profile') getProfile(@User() user: UserEntity) { return user; } @Get('email') getEmail(@User('email') email: string) { // data = 'email' return email; }
The data argument is whatever you pass into the decorator — @User('email') passes 'email' as data. This lets one decorator return either the whole object or a single field.

Metadata decorators

You met @SetMetadata() with guards. Wrapping it in a named decorator makes intent obvious and avoids magic strings scattered around the code:

import { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles); // usage — far clearer than @SetMetadata('roles', ['admin']) @Roles('admin') @Get('reports') getReports() {}

The matching guard reads it back with reflector.get('roles', context.getHandler()), exactly as in the guards lesson.

Composing decorators

applyDecorators() bundles several decorators into one reusable decorator — great for a common combination like "requires auth + a role + Swagger docs":

import { applyDecorators, UseGuards } from '@nestjs/common'; export function Auth(...roles: string[]) { return applyDecorators( Roles(...roles), UseGuards(AuthGuard, RolesGuard), ); } // one decorator does it all @Auth('admin') @Get('reports') getReports() {}

The full request lifecycle

Now that you have met every building block, here is the order each request flows through:

Request -> Middleware -> Guards -> Interceptors (pre-handler) -> Pipes -> Route Handler (your controller method) -> Interceptors (post-handler) -> Exception Filters (only if something threw) -> Response
Knowing this order is what makes you fluent in NestJS. When you ask "where should this logic go?", the answer is usually "at the stage that has the right context" — auth in guards, transformation in pipes/interceptors, errors in filters.

Summary

Custom decorators make controllers expressive: createParamDecorator extracts request data (like @User()), SetMetadata wrappers (like @Roles()) attach metadata for guards, and applyDecorators composes several into one. With the full lifecycle — middleware, guards, interceptors, pipes, handler, filters — now clear, you have completed Phase 3 and can build robust, well-structured NestJS request handling.