Chapter 08 — Middleware

Chapter 08 — Middleware

Hey everyone! Welcome back to Namaste NestJS! 🙏

If you've touched Express before, you already know middleware — those (req, res, next) functions that run on the way in. Nest keeps this familiar concept but gives it a proper home. Middleware is the very first checkpoint a request hits, making it perfect for logging, headers, cookies, and anything that needs the raw request early.

What we will cover:

  • What middleware is and when it runs
  • Middleware's exact place in the pipeline
  • Functional middleware (the simple kind)
  • Class middleware (the injectable kind)
  • Applying middleware with configure() + routes
  • The all-important next()
  • Middleware vs guards vs interceptors — who does what
  • Common mistakes
  • Interview Questions

1. What is Middleware?

┌─────────────────────────────────────────────────────────────┐
│                    WHAT IS MIDDLEWARE?                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   A function that runs BEFORE the route handler,           │
│   with access to the raw request & response objects.       │
│                                                             │
│   It can:                                                   │
│     • read/modify req and res                              │
│     • end the request early (send a response)             │
│     • pass control onward by calling next()               │
│                                                             │
│   req ──→ [ middleware ] ──→ next() ──→ (rest of pipeline)  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

A simple analogy: middleware is the security guard at the building's front door. Every visitor passes them first. They can log your arrival, hand you a visitor badge (modify the request), or turn you away — before you ever reach a department.


2. Middleware's Place in the Pipeline

Middleware runs first, even before guards. This is the key distinction.

THE PIPELINE (where middleware sits):
──────────────────────────────────────
  Request
     │
     ▼
  ★ MIDDLEWARE ★  →  Guards  →  Interceptors  →  Pipes  →  HANDLER
     │
   runs earliest — has the raw req/res,
   but does NOT know which handler will run yet

Important: because middleware runs before routing is fully resolved, it doesn't know which controller method will handle the request. If you need that context (like the target handler or its metadata), you want a guard or interceptor instead — coming up next.


3. Functional Middleware (The Simple Kind)

The simplest middleware is a plain function — identical to Express:

// src/logger.middleware.ts
import { Request, Response, NextFunction } from 'express';

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`[${req.method}] ${req.originalUrl}`);   // log the request
  next();   // 👈 MUST call next() to continue, or the request hangs forever!
}

Use functional middleware when it has no dependencies and just does one small thing.


4. Class Middleware (The Injectable Kind)

When your middleware needs a service (say, to read config or hit a cache), make it a class implementing NestMiddleware so it can use dependency injection.

// src/logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  // you can inject services here, e.g. constructor(private cfg: ConfigService) {}

  use(req: Request, res: Response, next: NextFunction) {
    console.log(`[${req.method}] ${req.originalUrl}`);
    next();   // 👈 always call next()
  }
}
FUNCTIONAL vs CLASS MIDDLEWARE:
────────────────────────────────
  Functional → no dependencies, quick and simple
  Class      → needs DI (services), implements NestMiddleware

5. Applying Middleware — configure()

Unlike other providers, middleware isn't attached with a decorator. You wire it up inside a module that implements NestModule and its configure() method.

import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';

@Module({
  controllers: [CatsController],
})
export class CatsModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)          // which middleware
      .forRoutes('cats');               // 👈 apply to all /cats routes
  }
}

You can target routes precisely:

// every route in the app:
consumer.apply(LoggerMiddleware).forRoutes('*');

// only GET /cats:
consumer.apply(LoggerMiddleware)
        .forRoutes({ path: 'cats', method: RequestMethod.GET });

// everything EXCEPT some routes:
consumer.apply(LoggerMiddleware)
        .exclude('cats/health')
        .forRoutes('cats');

Q: Can I apply middleware to the whole app more simply?

A: Yes — for functional middleware you can also call app.use(logger) in main.ts, exactly like Express. The configure() approach is preferred when you need DI or fine-grained route control.


6. The All-Important next()

This is the #1 middleware bug, so it gets its own section.

┌─────────────────────────────────────────────────────────────┐
│   GOLDEN RULE OF MIDDLEWARE                                 │
│                                                             │
│   Either call next()  OR  end the response (res.send).     │
│   NEVER do neither — the request will hang until timeout.  │
│                                                             │
│   next()        → "I'm done, continue the pipeline"        │
│   res.json(...) → "I'm handling it here, stop the pipeline"│
│   (nothing)     → 💀 request frozen forever                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Trace an auth-style middleware that can go two ways:

HAND TRACE:  a "block unverified" middleware
────────────────────────────────────────────────
  use(req, res, next):
    has valid API key?
      ✅ yes → next()          → request continues to the handler
      ❌ no  → res.status(401)  → request ends HERE, handler never runs
                  .send('nope')

7. Middleware vs Guards vs Interceptors

These three overlap in beginners' minds. Here's the clean separation:

FeatureRunsBest forKnows the handler?
MiddlewareFirstLogging, headers, cookies, raw req workNo
GuardAfter middlewareAuth: "can this request proceed?"Yes
InterceptorAround handlerTransform response, timing, cachingYes

In simple words: if you don't need to know which route/handler is targeted, middleware is fine. The moment you need "should this specific user access this endpoint?", you want a guard (next chapter).


8. Little Traps Beginners Fall Into

┌─────────────────────────────────────────────────────────────┐
│   COMMON MISTAKES ❌                                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Forgetting next() → request hangs forever. 💀             │
│                                                             │
│  Putting auth logic in middleware when a GUARD is cleaner  │
│  (guards know the handler and integrate with @UseGuards).  │
│                                                             │
│  Expecting middleware to see route metadata / the DTO.     │
│     → It runs before routing resolves. Use an interceptor. │
│                                                             │
│  Registering class middleware in providers[] and expecting │
│  it to run — you must wire it in configure().              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Interview Questions — Quick Fire!

Q: What is middleware in NestJS?

"Middleware is a function that runs before the route handler and has access to the raw request and response objects. It can read or modify them, end the request early, or pass control to the next step by calling next. Nest's middleware is the same concept as Express middleware, and it runs first in the request pipeline."

Q: What's the difference between functional and class middleware?

"Functional middleware is a plain function taking req, res, and next — simple and dependency-free. Class middleware is a class decorated with @Injectable that implements NestMiddleware and its use method, which lets it participate in dependency injection and receive services in its constructor."

Q: How do you apply middleware to specific routes?

"You make the module implement NestModule and define a configure method that receives a MiddlewareConsumer. You then call consumer.apply with the middleware and forRoutes with a path, controller, or method to target it. You can also exclude specific routes."

Q: Why is calling next() important?

"next passes control to the next step in the pipeline. If middleware neither calls next nor sends a response, the request hangs indefinitely until it times out. So every middleware must either call next to continue or end the response itself."

Q: When should you use a guard instead of middleware?

"When the logic depends on knowing which handler will run or needs route metadata — for example authorization. Middleware runs before routing is resolved, so it doesn't know the target handler. Guards run after middleware, have access to the execution context, and integrate cleanly with @UseGuards, making them the right tool for auth."


Key Points to Remember

ConceptKey Takeaway
MiddlewareRuns first; has raw req/res; same idea as Express.
Two kindsFunctional (simple) · class (implements NestMiddleware, uses DI).
Applying itModule implements NestModule → configure() → apply().forRoutes().
next()Must call next() or end the response, or the request hangs.
Doesn't know handlerRuns before routing resolves — use a guard for auth.

What's Next?

Middleware can't answer "is this user allowed to hit this specific endpoint?" — but Guards can. In Chapter 09 we build guards: classes that return true or false to allow or block a request, the foundation of authentication and role-based access. This is where your API starts getting secure.

Keep coding, keep shipping! See you in the next one!