NestJS

Lesson 09

Request lifecycle: middleware and guards

Place middleware, guards, pipes, and interceptors according to the NestJS request lifecycle.

Good Code

request-context.middleware.ts
import {
  CanActivate,
  ExecutionContext,
  Injectable,
  NestMiddleware,
} from "@nestjs/common";
import type { NextFunction, Response } from "express";

@Injectable()
export class RequestContextMiddleware implements NestMiddleware {
  // Middleware attaches low-level request metadata.
  use(request: RequestWithContext, _response: Response, next: NextFunction) {
    request.requestId = crypto.randomUUID();
    next();
  }
}

@Injectable()
export class AdminGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    const request = context.switchToHttp().getRequest<RequestWithContext>();
    return request.user?.role === "admin";
  }
}

Bad Code

auth.middleware.ts
import { Injectable, NestMiddleware } from "@nestjs/common";

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  use(request: any, response: any, next: () => void) {
    // Access policy hidden in middleware can bypass controller metadata.
    if (request.path.startsWith("/admin") && request.user?.role !== "admin") {
      response.status(403).send("Forbidden");
      return;
    }
    next();
  }
}

Review Notes

What to review

Good Code

The good version uses middleware for request metadata and a guard for authorization. Each concern maps to a lifecycle stage.

Bad Code

The bad version hides route access policy inside path-string checks. Controller metadata and route decorators no longer tell the full security story.

Takeaways

  • Middleware is for low-level request work; guards decide access before pipes and controller logic run.