We use cookies to enhance your experience on the site
CodeWorlds

Request Lifecycle in NestJS - the journey of a dispatch through the empire

Architect of the empire! Architect Vitruvius, master of buildings and systems, has prepared the most important lesson of this module for you. You've already learned about middleware, guards, interceptors, pipes, custom decorators, and exception filters. Now it's time to see how ALL of this comes together as one whole - the full lifecycle of an HTTP request in NestJS!

The full journey of a dispatch through the empire

Imagine that a dispatch (HTTP request) sets out from a distant province to Caesar. Along the way, it passes through many checkpoints. Here is the exact order:

1Incoming HTTP request
2        |
3        v
4  1. MIDDLEWARE (Gate guard)
5        |
6        v
7  2. GUARDS (Permission check)
8        |
9        v
10  3. INTERCEPTORS - before (Chronicler - start)
11        |
12        v
13  4. PIPES (Data quality control)
14        |
15        v
16  5. ROUTE HANDLER (Caesar makes the decision)
17        |
18        v
19  6. INTERCEPTORS - after (Chronicler - end)
20        |
21        v
22  7. EXCEPTION FILTERS (Tribunal - in case of error)
23        |
24        v
25  HTTP Response

Each stage in detail

1. Middleware - the gate guard

Middleware executes FIRST, before NestJS even knows which controller method will be called. It has access to the raw Request and Response objects:

1// Middleware executes FIRST
2@Injectable()
3export class LoggerMiddleware implements NestMiddleware {
4  use(req: Request, res: Response, next: NextFunction) {
5    console.log('[1. MIDDLEWARE] Dispatch reached the gate');
6    console.log('Method: ' + req.method + ', Path: ' + req.path);
7    next(); // Pass along
8  }
9}

2. Guards - permission check

Guards execute AFTER middleware but BEFORE interceptors and pipes. Their only job is to answer the question: "Does this request have the right to continue?":

1// Guard executes SECOND
2@Injectable()
3export class AuthGuard implements CanActivate {
4  canActivate(context: ExecutionContext): boolean {
5    console.log('[2. GUARD] Checking legionary permissions');
6    const request = context.switchToHttp().getRequest();
7    return !!request.headers.authorization; // true = let through, false = block
8  }
9}

3. Interceptors (before) - chronicler start

Interceptors have a unique property - they execute twice: once BEFORE the handler and once AFTER it. The "before" phase is the code before

next.handle()
:

1// Interceptor - BEFORE phase (third step)
2@Injectable()
3export class TimingInterceptor implements NestInterceptor {
4  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
5    const start = Date.now();
6    console.log('[3. INTERCEPTOR before] Chronicler starts recording');
7
8    return next.handle().pipe(
9      // This part is the AFTER phase (sixth step)
10      tap(() => {
11        const duration = Date.now() - start;
12        console.log('[6. INTERCEPTOR after] Time: ' + duration + 'ms');
13      }),
14    );
15  }
16}

4. Pipes - data quality control

Pipes execute DIRECTLY before the handler. They transform and validate input data:

1// Pipe executes FOURTH
2@Injectable()
3export class ParseLegionIdPipe implements PipeTransform {
4  transform(value: any) {
5    console.log('[4. PIPE] Validating input data: ' + value);
6    const id = parseInt(value, 10);
7    if (isNaN(id)) {
8      throw new BadRequestException('ID must be a number!');
9    }
10    return id;
11  }
12}

5. Route Handler - Caesar's decision

The handler is the target controller method - this is where business logic executes:

1@Controller('legiones')
2export class LegionController {
3  @Get(':id')
4  @UseGuards(AuthGuard)
5  @UseInterceptors(TimingInterceptor)
6  findLegion(@Param('id', ParseLegionIdPipe) id: number) {
7    console.log('[5. HANDLER] Caesar processes the query about legion ' + id);
8    return { id, name: 'Legio X Gemina', commander: 'Marcus' };
9  }
10}

6. Interceptors (after) - chronicler end

After the handler completes, control returns to the interceptor - to the code inside

pipe(tap(...))
. Here you can transform the response, log time, etc.

7. Exception Filters - the tribunal

Exception Filters execute ONLY when an exception is thrown - at any stage. If a guard throws ForbiddenException, the filter catches it. If a pipe throws BadRequestException - it catches that too:

1// Exception Filter - catches errors from EVERY stage
2@Catch(HttpException)
3export class ImperiumExceptionFilter implements ExceptionFilter {
4  catch(exception: HttpException, host: ArgumentsHost) {
5    console.log('[7. EXCEPTION FILTER] Tribunal handles the error');
6    const ctx = host.switchToHttp();
7    const response = ctx.getResponse();
8    response.status(exception.getStatus()).json({
9      error: exception.message,
10      timestamp: new Date().toISOString(),
11    });
12  }
13}

Practical implications of the order

Understanding the order is key for debugging:

1// Example: What happens when a guard rejects the request?
2// 1. Middleware - WILL EXECUTE (logging)
3// 2. Guard - WILL REJECT (throws ForbiddenException)
4// 3. Interceptor before - will NOT execute
5// 4. Pipe - will NOT execute
6// 5. Handler - will NOT execute
7// 6. Interceptor after - will NOT execute
8// 7. Exception Filter - WILL EXECUTE (handles ForbiddenException)
9
10// Example: What happens when a pipe rejects the data?
11// 1. Middleware - WILL EXECUTE
12// 2. Guard - WILL EXECUTE (passes through)
13// 3. Interceptor before - WILL EXECUTE
14// 4. Pipe - WILL REJECT (throws BadRequestException)
15// 5. Handler - will NOT execute
16// 6. Interceptor after - will NOT execute (no response)
17// 7. Exception Filter - WILL EXECUTE (handles BadRequestException)

Debugging the pipeline

A practical debugging technique - add logs at each stage:

1// middleware
2console.log('[MW] ' + req.method + ' ' + req.path);
3
4// guard
5console.log('[GUARD] User: ' + request.user?.name);
6
7// interceptor before
8console.log('[INT-B] Handler: ' + context.getHandler().name);
9
10// pipe
11console.log('[PIPE] Value: ' + value + ' -> ' + transformed);
12
13// handler
14console.log('[HANDLER] Processing...');
15
16// interceptor after
17console.log('[INT-A] Duration: ' + duration + 'ms');
18
19// exception filter
20console.log('[FILTER] Error: ' + exception.message);

When you see the logs, you can precisely trace the request's journey through the entire pipeline and quickly find at which stage a problem occurs.

Lifecycle summary

Remember this order like a marching legion column:

  1. Middleware - first checkpoint (raw request)
  2. Guards - permission check (let through/deny)
  3. Interceptors before - start of observation
  4. Pipes - validation and data transformation
  5. Handler - business logic
  6. Interceptors after - end of observation, response transformation
  7. Exception Filters - error handling (when something went wrong)

Knowing this lifecycle is the foundation of every advanced NestJS developer - like knowing battle strategy for a centurion!

Go to CodeWorlds