Fullstack CourseLearn by building
Back to week 1

Topic

Request pipeline (middleware → guards → pipes → handler → filters)

Definition

The Nest request pipeline is the fixed sequence of extension points — middleware, guards, interceptors, pipes, the route handler, and exception filters — through which every HTTP request passes, in that order, before a response is sent.

In simpler words

A request runs a gauntlet: logging/setup first, then "are you allowed here", then "is your data valid", then your actual code, then "turn any error into JSON".

This is the single most useful Nest debugging model. Intermediate engineers do not guess — they map a status code to a pipeline stage and open the right file.

After this you can

  • Recite the pipeline order from memory including interceptors
  • Map 401/403/400/404/500 to the stage that usually produced them
  • Explain why auth checks belong in guards, not middleware
  • Say what CorrelationIdMiddleware, JwtAuthGuard, RolesGuard, ValidationPipe, and AllExceptionsFilter each do here
  • Decide whether a cross-cutting concern belongs in middleware, guard, interceptor, pipe, or filter

The ordered pipeline

Definition

Nest processes an incoming request through middleware, then applicable guards, then interceptors (pre-handler), then pipes, then the matched controller method, then interceptors (post-handler), and finally exception filters if anything along the way throws.

In simpler words

Middleware runs for every matching route first; guards decide access; pipes check/transform input; your handler runs; filters catch anything that went wrong and turn it into JSON.

Memorize: Middleware → Guards → Interceptors (pre) → Pipes → Controller → Service → Interceptors (post) → Exception filters (on throw from any earlier stage).

Anything thrown becomes a filter’s job. Anything invalid in a DTO should never reach the service if ValidationPipe is global.

Interceptors can wrap the handler (timing, mapping responses) but should not replace guards for authz or pipes for DTO validation.

The pipeline as a diagram

HTTP request
  → Middleware   (CorrelationIdMiddleware)
  → Guards       (JwtAuthGuard → RolesGuard)
  → Interceptors (optional pre)
  → Pipes        (ValidationPipe on @Body/@Query/@Param)
  → Controller handler → Service (TypeORM)
  → Interceptors (optional post) → Response JSON
  // throw anywhere → AllExceptionsFilter → error JSON + correlationId

When debugging, start from the status code, then walk this list — do not open random files.

Status codes → stages (debugging map)

Definition

HTTP status codes in a Nest API are strong signals for which pipeline stage failed: authentication, authorization, validation, domain lookup, or an unhandled exception.

In simpler words

Learn this map and you stop treating every failure as “something in the service.”

401 Unauthorized — usually JwtAuthGuard (missing/invalid cookie or bearer). Check @Public() if the route should skip auth.

403 Forbidden — usually RolesGuard (authenticated user, wrong role). Check @Roles(...) and the user’s role claim/row.

400 Bad Request — usually ValidationPipe / DTO (shape, types, forbidNonWhitelisted). Check the DTO and global pipe options.

404 Not Found — usually the service throwing NotFoundException after a DB miss.

500 Internal Server Error — unhandled throw; AllExceptionsFilter still formats JSON, but the bug is in your code or infrastructure.

Quick triage checklist

401 → cookie/JWT / @Public mismatch
403 → @Roles vs user.role
400 → DTO + ValidationPipe (whitelist/forbid/transform)
404 → service findOne + NotFoundException
500 → stack + correlationId in logs/response

Carry the correlationId from middleware into logs so FE and BE share one request id.

Middleware and guards in this repo

Definition

Middleware executes for every matching route before Nest resolves a controller and cannot access route-handler metadata, while guards run per-route inside the pipeline and can read decorator metadata such as @Public or @Roles to decide access.

In simpler words

Middleware cannot see which controller method will run or its decorators; guards can — that is exactly why auth belongs in guards, not middleware.

CorrelationIdMiddleware runs first for every request (registered with consumer.apply(...).forRoutes("*") in AppModule): it reads or generates an x-correlation-id and attaches it to the request/response, with no knowledge of auth.

JwtAuthGuard and RolesGuard are registered globally as APP_GUARD providers — they run for every route by default, reading @Public() to skip auth entirely and @Roles(...) to check the authenticated user’s role.

Guard order matters: authentication must attach request.user before authorization reads it. JwtAuthGuard before RolesGuard is the correct pairing.

Where each one lives

// AppModule — middleware, no route metadata
consumer.apply(CorrelationIdMiddleware).forRoutes('*');

// AuthModule — guards, read route metadata via Reflector
providers: [
  { provide: APP_GUARD, useClass: JwtAuthGuard },
  { provide: APP_GUARD, useClass: RolesGuard },
]

Guards can ask "does this handler have @Public()?" — middleware structurally cannot.

Mistake: authorization in middleware

// Wrong — middleware checking a JWT and role
app.use((req, res, next) => {
  const role = decodeJwt(req.cookies.access_token)?.role;
  if (role !== 'admin') return res.sendStatus(403);
  next();
});

// Right — a guard that reads @Roles metadata
@Delete(':id')
@Roles('admin')
remove(@Param('id') id: string) {}

Middleware has no clean way to read @Roles metadata per-route and does not integrate with Nest’s testing utilities the way guards do.

Pipes, interceptors, and filters

Definition

Pipes transform and validate arguments immediately before a handler executes; interceptors wrap handler execution for cross-cutting observation or mapping; exception filters catch any exception thrown during earlier stages and translate it into an HTTP response.

In simpler words

Pipes are the last gate before your code runs; interceptors decorate success paths; filters are the safety net for failures.

main.ts registers one global ValidationPipe with whitelist, forbidNonWhitelisted, and transform — every @Body()/@Query() DTO across the whole app is validated the same way.

main.ts also registers AllExceptionsFilter — shared { statusCode, error, message, details, correlationId } JSON so the frontend can handle errors uniformly.

Use interceptors for logging duration or mapping response envelopes — not for rejecting unauthorized users (that is a guard’s job).

Global pipe + filter (apps/api/src/main.ts)

app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
  }),
);
app.useGlobalFilters(new AllExceptionsFilter());

One pipe, one filter, applied everywhere — that consistency is what makes the frontend’s error handling simple.

Mistake: validate inside the service only

// Wrong — service manually checks fields after the controller accepted anything
create(body: any) {
  if (!body.title) throw new Error('missing');
}

// Right — DTO + ValidationPipe before the handler runs
create(@Body() dto: CreateTicketDto) {
  return this.ticketsService.create(dto, user.id);
}

Late validation lets bad payloads into the controller and produces inconsistent error shapes.

Choosing the right extension point

Definition

Each Nest extension point has a narrow responsibility; putting a concern in the wrong stage creates code that is hard to test, hard to skip per-route, and easy to bypass.

In simpler words

Ask: setup, access, transform, observe, or recover — then pick middleware, guard, pipe, interceptor, or filter.

Setup / correlation / raw Express concerns → middleware.

Can this user call this route? → guard (+ metadata decorators).

Is this argument valid / correctly typed? → pipe + DTO.

Time the call / wrap the response → interceptor.

Turn any failure into a stable HTTP body → exception filter.

Keep in mind

  • When something 401s, check guards/@Public first. When it 400s, check pipes/DTOs. When it 500s, check the filter’s server-error branch and logs.
  • Never put an authorization decision in middleware in this codebase — guards are the convention.
  • Global pipes/filters are configured once in main.ts — look there before adding a local one.
  • Authn before authz: JwtAuthGuard must run before RolesGuard.

Test

Check your understanding

At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥ 80% (enforced by the API) to save progress.

Checking your session…

10 questions · concept 3 · syntax 2 · practical 3 · logic 2

Concept1. Which statement best defines the Nest request pipeline?
Syntax2. Which code-level choice matches the Nest request pipeline?
Practical3. A reviewer spots a bug related to the Nest request pipeline. What is the right fix?
Logic4. Which reasoning correctly explains the Nest request pipeline?
Concept5. Which boundary does correct use of the Nest request pipeline preserve?
Practical6. What is the safest next step when applying the Nest request pipeline?
Syntax7. Which implementation direction matches the rule for the Nest request pipeline?
Logic8. Which consequence follows from applying the Nest request pipeline correctly?
Concept9. Which claim about the Nest request pipeline is true in this Nest + Postgres monorepo?
Practical10. Which team practice best demonstrates the Nest request pipeline?