NestJS
02 / 04

DI, Guards & Interceptors

DI, Guards & Interceptors

Dependency Injection

// Standard injection via constructor
@Injectable()
export class OrdersService {
  constructor(
    private readonly usersService: UsersService,
    private readonly emailService: EmailService,
    private readonly configService: ConfigService,
  ) {}
}

// Custom provider — factory
@Module({
  providers: [
    {
      provide: 'DATABASE_CONNECTION',
      useFactory: async (config: ConfigService) => {
        return createConnection(config.get('DATABASE_URL'));
      },
      inject: [ConfigService],
    },
    {
      provide: 'APP_CONFIG',
      useValue: { timeout: 5000, retries: 3 },
    },
  ],
})
export class AppModule {}

// Inject custom provider
@Injectable()
export class AppService {
  constructor(
    @Inject('DATABASE_CONNECTION') private readonly db: Connection,
    @Inject('APP_CONFIG') private readonly config: { timeout: number },
  ) {}
}

// ConfigModule (built-in, global)
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env' }),
  ],
})
export class AppModule {}

// Use anywhere
constructor(private configService: ConfigService) {}
const dbUrl = this.configService.get<string>('DATABASE_URL');

Guards

import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(private jwtService: JwtService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<Request>();
    const token = this.extractToken(request);
    if (!token) throw new UnauthorizedException();

    try {
      const payload = await this.jwtService.verifyAsync(token);
      request['user'] = payload;  // attach to request
      return true;
    } catch {
      throw new UnauthorizedException();
    }
  }

  private extractToken(req: Request): string | null {
    const [type, token] = req.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : null;
  }
}

// Role-based guard using custom decorator
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!requiredRoles) return true;
    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.some(role => user.roles?.includes(role));
  }
}

// Custom decorator
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

// Applying guards
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('admin-only')
adminRoute() { ... }

// Global guard
app.useGlobalGuards(new JwtAuthGuard(jwtService));

Interceptors

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

// Transform response — wrap in { data: ... }
@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(map(data => ({ data, success: true })));
  }
}

// Logging interceptor
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const req = context.switchToHttp().getRequest();
    const start = Date.now();
    console.log(`--> ${req.method} ${req.url}`);
    return next.handle().pipe(
      tap(() => console.log(`<-- ${req.method} ${req.url} ${Date.now() - start}ms`)),
    );
  }
}

// Apply
@UseInterceptors(TransformInterceptor)
@Get()
findAll() { ... }

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

Pipes & Custom Decorators

// Built-in pipes
@Param('id', ParseIntPipe) id: number
@Param('id', ParseUUIDPipe) id: string
@Body(new ValidationPipe()) dto: CreateUserDto

// Custom pipe
@Injectable()
export class TrimPipe implements PipeTransform {
  transform(value: any) {
    if (typeof value === 'string') return value.trim();
    return value;
  }
}

// Custom param decorator
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const CurrentUser = createParamDecorator(
  (data: string | undefined, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return data ? request.user?.[data] : request.user;
  },
);

// Usage
@Get('profile')
getProfile(@CurrentUser() user: UserPayload) {
  return user;
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free