| name | nestjs-guards-interceptors |
| user-invocable | false |
| description | Use when nestJS guards and interceptors for auth, logging, and transformation. Use when implementing cross-cutting concerns. |
| allowed-tools | ["Bash","Read"] |
NestJS Guards and Interceptors
Master NestJS guards and interceptors for implementing authentication,
authorization, logging, and request/response transformation.
Guards Fundamentals
Understanding CanActivate and ExecutionContext.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class BasicGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return this.validateRequest(request);
}
private validateRequest(request: any): boolean {
return !!request.headers.authorization;
}
}
@Injectable()
export class ContextAwareGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const httpContext = context.switchToHttp();
const request = httpContext.getRequest();
const response = httpContext.getResponse();
const handler = context.getHandler();
const controller = context.getClass();
console.log(`Handler: ${handler.name}`);
console.log(`Controller: ${controller.name}`);
return true;
}
}
import { Controller, Get, UseGuards } from '@nestjs/common';
@Controller('users')
@UseGuards(BasicGuard)
export class UserController {
@Get()
findAll() {
return [];
}
@Get('profile')
@UseGuards(ContextAwareGuard)
getProfile() {
return { name: 'John' };
}
}
Authentication Guards
JWT, session, and API key authentication patterns.
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException('No token provided');
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: process.env.JWT_SECRET,
});
request['user'] = payload;
} catch {
();
}
;
}
(: ): | {
[, token] = request..?.() ?? [];
=== ? token : ;
}
}
()
{
(: ): {
request = context.().();
(!request. || !request..) {
();
}
;
}
}
()
{
() {}
(: ): {
request = context.().();
apiKey = request.[];
(!apiKey) {
();
}
validApiKey = ..();
(apiKey !== validApiKey) {
();
}
;
}
}
()
{
() {}
(: ): <> {
request = context.().();
token = .(request);
(token) {
{
payload = ..(token);
request[] = payload;
;
} {}
}
apiKey = request.[];
(apiKey === ..()) {
;
}
();
}
(: ): | {
[, token] = request..?.() ?? [];
=== ? token : ;
}
}
Role-Based Authorization Guards
RBAC patterns with decorators.
import { SetMetadata } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
export enum Role {
USER = 'user',
ADMIN = 'admin',
MODERATOR = 'moderator',
}
export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(, [
context.(),
context.(),
]);
(!requiredRoles) {
;
}
request = context.().();
user = request.;
(!user) {
();
}
hasRole = requiredRoles.( user.?.(role));
(!hasRole) {
();
}
;
}
}
()
(, )
{
()
(.)
() {
[];
}
()
(., .)
() {
{ : };
}
}
= ;
= () =>
(, permissions);
()
{
() {}
(: ): {
requiredPermissions = ..<[]>(
,
[context.(), context.()],
);
(!requiredPermissions) {
;
}
request = context.().();
user = request.;
hasPermission = requiredPermissions.(
user.?.(permission),
);
(!hasPermission) {
();
}
;
}
}
()
{
() {}
(: ): <> {
request = context.().();
user = request.;
resourceId = request..;
resource = ..(resourceId);
(!resource) {
();
}
(resource. !== user. && !user..(.)) {
();
}
request[] = resource;
;
}
}
Interceptors Fundamentals
NestInterceptor and response transformation.
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
}
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, <T>> {
(
: ,
: ,
): <<T>> {
next.().(
( ({
data,
: ().(),
: context.().().,
})),
);
}
}
<T> {
: T;
: ;
: ;
}
()
{
(: , : ): <> {
next.().(
( {
.(, err);
();
}),
);
}
}
()
()
{
()
()
() {
[{ : , : }];
}
}
Logging Interceptors
Advanced logging patterns.
import { Logger } from '@nestjs/common';
@Injectable()
export class RequestLoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger(RequestLoggingInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { method, url, body } = request;
const userAgent = request.get('user-agent') || '';
this.logger.log(`Incoming Request: ${method} ${url}`);
this.logger.debug(`User Agent: ${userAgent}`);
this.logger.debug(`Body: ${JSON.stringify(body)}`);
const now = Date.now();
next.().(
({
: {
response = context.().();
..(
,
);
},
: {
..(
,
err.,
);
},
}),
);
}
}
()
{
logger = (.);
(: , : ): <> {
request = context.().();
{ method, url } = request;
startTime = .();
next.().(
( {
duration = .() - startTime;
(duration > ) {
..();
} {
..();
}
}),
);
}
}
Response Transformation Interceptors
Shaping API responses consistently.
@Injectable()
export class ResponseWrapperInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) => {
const response = context.switchToHttp().getResponse();
return {
statusCode: response.statusCode,
message: 'Success',
data,
};
}),
);
}
}
interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}
@Injectable()
export class PaginationInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: ): <> {
next.().(
( {
(data && data === && data) {
{ items, total } = data;
request = context.().();
page = (request..) || ;
pageSize = (request..) || ;
{
items,
total,
page,
pageSize,
: .(total / pageSize),
};
}
data;
}),
);
}
}
()
{
(: , : ): <> {
next.().(
( {
.(data);
}),
);
}
(: ): {
(.(obj)) {
obj.( .(item));
}
(obj !== && obj === ) {
.(obj).( {
(value !== ) {
acc[key] = .(value);
}
acc;
}, {});
}
obj;
}
}
Caching Interceptors
Implementing caching strategies.
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
async intercept(
context: ExecutionContext,
next: CallHandler,
): Promise<Observable<any>> {
const request = context.switchToHttp().getRequest();
const cacheKey = `${request.method}:${request.url}`;
const cachedResponse = await this.cacheManager.get(cacheKey);
if (cachedResponse) {
return of(cachedResponse);
}
return next.handle().pipe(
tap( (response) => {
..(cacheKey, response, );
}),
);
}
}
= ;
= () => (, key);
()
{
() {}
(
: ,
: ,
): <<>> {
cacheKey = ..(, context.());
(!cacheKey) {
next.();
}
cached = ..(cacheKey);
(cached) {
(cached);
}
next.().(
( (response) => {
..(cacheKey, response);
}),
);
}
}
()
{
()
()
() {
..();
}
}
Timeout Interceptors
Handling request timeouts.
import { timeout, catchError } from 'rxjs/operators';
import { throwError, TimeoutError } from 'rxjs';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
timeout(5000),
catchError((err) => {
if (err instanceof TimeoutError) {
return throwError(() => new RequestTimeoutException());
}
return throwError(() => err);
}),
);
}
}
export const TIMEOUT_METADATA = 'timeout';
export const Timeout = (milliseconds: ) =>
(, milliseconds);
()
{
() {}
(: , : ): <> {
timeoutValue =
..(, context.()) || ;
next.().(
(timeoutValue),
( {
(err ) {
( ());
}
( err);
}),
);
}
}
()
{
()
()
() {
..();
}
}
Pipes
Validation and transformation pipes.
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';
import { ValidationPipe } from '@nestjs/common';
@Controller('users')
export class UserController {
@Post()
create(@Body(new ValidationPipe()) createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
}
@Injectable()
export class CustomValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
value;
}
= (metatype, value);
errors = ();
(errors. > ) {
messages = errors.( ({
: err.,
: err.,
}));
({ : messages });
}
value;
}
(: ): {
: [] = [, , , , ];
!types.(metatype);
}
}
()
<, > {
(: , : ): {
val = (value, );
((val)) {
();
}
val;
}
}
()
() {
..(id);
}
()
{
() {}
() {
( value !== || value === ) {
value;
}
result = { ...value };
..( {
result[field];
});
result;
}
}
()
{
() {}
() {
value !== && value !== ? value : .;
}
}
Exception Filters
Custom exception handling.
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message,
});
}
}
@Catch()
{
logger = (.);
(: , : ) {
ctx = host.();
response = ctx.<>();
request = ctx.<>();
status =
exception
? exception.()
: .;
message =
exception
? exception.
: ;
..(
,
exception ? exception. : ,
);
response.(status).({
: status,
: ().(),
: request.,
message,
});
}
}
()
{
(: , : ) {
ctx = host.();
response = ctx.<>();
request = ctx.<>();
exceptionResponse = exception.();
errors =
exceptionResponse === && exceptionResponse
? exceptionResponse[]
: exceptionResponse;
response.(.).({
: .,
: ().(),
: request.,
errors,
});
}
}
()
( ())
{}
() {
app = .();
app.( ());
app.();
}
Middleware
Function and class middleware.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
private logger = new Logger('HTTP');
use(req: Request, res: Response, next: NextFunction) {
const { method, originalUrl } = req;
const startTime = Date.now();
res.on('finish', () => {
const { statusCode } = res;
const duration = Date.now() - startTime;
this.logger.log(`${method} ${originalUrl} ${statusCode} - ${duration}ms`);
});
next();
}
}
() {
.();
();
}
()
{
() {}
() {
token = req..?.()[];
(!token) {
();
}
{
user = ..(token);
req[] = user;
();
} (error) {
();
}
}
}
()
{
() {
res.(, );
res.(, );
res.(, );
(req. === ) {
res.();
} {
();
}
}
}
{ , , } ;
({
: [],
: [],
})
{
() {
consumer
.()
.();
consumer
.()
.(
{ : , : . },
{ : , : . },
)
.();
}
}
Request Lifecycle and Execution Order
Understanding the order of execution.
@Controller('demo')
export class DemoController {
private readonly logger = new Logger(DemoController.name);
@Post()
@UseGuards(DemoGuard)
@UseInterceptors(DemoInterceptor)
@UsePipes(DemoPipe)
create(@Body() data: any) {
this.logger.log('5. Controller method executed');
return data;
}
}
@Injectable()
export class DemoGuard implements CanActivate {
private readonly logger = new Logger(DemoGuard.name);
canActivate(context: ExecutionContext): {
..();
;
}
}
()
{
logger = (.);
(: , : ): <> {
..();
next.().(
( ..()),
);
}
}
()
{
logger = (.);
() {
..();
value;
}
}
Testing Guards and Interceptors
Unit testing patterns.
import { Test, TestingModule } from '@nestjs/testing';
import { ExecutionContext } from '@nestjs/common';
describe('JwtAuthGuard', () => {
let guard: JwtAuthGuard;
let jwtService: JwtService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
JwtAuthGuard,
{
provide: JwtService,
useValue: {
verifyAsync: jest.fn(),
},
},
],
}).compile();
guard = module.get<JwtAuthGuard>(JwtAuthGuard);
jwtService = module.get<JwtService>(JwtService);
});
it('should allow valid token', async () => {
const mockContext = {
switchToHttp: () => ({
getRequest: ({
: { : },
}),
}),
} ;
jest.(jwtService, ).({ : });
result = guard.(mockContext);
(result).();
});
(, () => {
mockContext = {
: ({
: ({
: { : },
}),
}),
} ;
jest.(jwtService, ).( ());
(guard.(mockContext))..(
,
);
});
});
(, {
: ;
( {
interceptor = ();
});
(, {
mockContext = {
: ({
: ({ : }),
}),
} ;
mockCallHandler = {
: ({ : }),
};
interceptor.(mockContext, mockCallHandler).( {
(result).();
(result).();
(result).();
(result.).({ : });
();
});
});
});
When to Use This Skill
Use nestjs-guards-interceptors when:
- Implementing authentication and authorization
- Adding logging and monitoring to your application
- Transforming request/response data consistently
- Implementing caching strategies
- Adding timeouts to requests
- Handling cross-cutting concerns
- Building middleware for request processing
- Creating reusable validation logic
- Implementing RBAC or ABAC patterns
- Adding performance monitoring
NestJS Guards and Interceptors Best Practices
- Single responsibility - Each guard/interceptor should have one clear purpose
- Use metadata - Leverage decorators and Reflector for configuration
- Chain appropriately - Understand execution order when combining
multiple guards/interceptors
- Error handling - Always handle errors gracefully in guards and interceptors
- Async operations - Use async/await for database calls in guards
- Global vs local - Apply guards/interceptors at appropriate scope
(global, controller, method)
- Test thoroughly - Write unit tests for all guards and interceptors
- Performance - Keep guards and interceptors lightweight
- Logging - Use Logger service instead of console.log
- Type safety - Use TypeScript generics for type-safe interceptors
NestJS Guards and Interceptors Common Pitfalls
- Wrong execution order - Not understanding middleware → guards →
interceptors → pipes flow
- Forgetting async - Not using async when guards perform database operations
- Missing error handling - Guards that don't throw appropriate exceptions
- Interceptor mutation - Mutating data in interceptors instead of transforming
- Circular dependencies - Guards that create circular dependency chains
- Global scope issues - Applying too many global guards/interceptors hurts performance
- Missing metadata - Forgetting to use Reflector to read custom metadata
- Pipe placement - Using pipes in wrong order with validation
- Exception filter scope - Not understanding filter precedence
- Memory leaks - Not properly cleaning up subscriptions in interceptors
Resources