# Middleware and Lifecycle

> Source: https://learn-nestjs.com/middleware-and-lifecycle/
> Part of Learn NestJS, free to read.

Middleware runs **before** everything Nest-specific — before guards, before interceptors, before the router has matched a handler. It is the raw Express or Fastify layer.

```typescript src/common/request-id.middleware.ts
@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const id = (req.headers['x-request-id'] as string) ?? randomUUID();
    (req as any).id = id;
    res.setHeader('x-request-id', id);
    next();                     // forget this and the request hangs forever
  }
}
```

```typescript src/app.module.ts
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(RequestIdMiddleware)
      .forRoutes('*');

    consumer
      .apply(RawBodyMiddleware)
      .forRoutes({ path: 'webhooks/*', method: RequestMethod.POST });

    consumer
      .apply(AuditMiddleware)
      .exclude('health', 'metrics')
      .forRoutes(OrdersController);
  }
}
```

Functional middleware works too and is simpler when you need no dependencies:

```typescript
export function requestId(req: Request, res: Response, next: NextFunction) {
  (req as any).id = randomUUID();
  next();
}
```

## The full pipeline

Worth memorising, because putting logic in the wrong layer is a common source of confusion:

```text
incoming request
  → middleware          (raw req/res; no route metadata yet)
  → guards              (may this proceed? sees decorators)
  → interceptors        (before)
  → pipes               (validate and transform arguments)
  → HANDLER
  → interceptors        (after)
  → exception filters   (on throw, at any stage)
```

The practical consequences:

- **Middleware cannot read route decorators.** It runs before routing, so `@Roles()` and `@Public()` are invisible to it. Anything decorator-driven must be a guard.
- **Guards cannot see the validated body.** Pipes run after guards, so a guard sees the raw payload.
- **Only interceptors and filters see the outcome.**

## Where middleware is genuinely right

Three cases, and they are all "this needs the raw request":

**Request context**, set once and read everywhere without threading it through:

```typescript
@Injectable()
export class ContextMiddleware implements NestMiddleware {
  use(req: Request, _res: Response, next: NextFunction) {
    requestContext.run(
      { requestId: (req as any).id, tenantId: (req as any).user?.tenantId },
      () => next(),
    );
  }
}
```

`AsyncLocalStorage` here rather than a request-scoped provider — scope is [contagious and expensive](/review/performance/).

**Raw body for signed webhooks:**

```typescript
consumer.apply(express.raw({ type: 'application/json' }))
        .forRoutes({ path: 'webhooks/stripe', method: RequestMethod.POST });
```

Stripe and most webhook providers sign the raw bytes. If a JSON parser runs first, the body is re-serialised and the signature no longer verifies.

**Third-party Express middleware** — `helmet`, `compression`, `cookie-parser`. These are written for Express and slot in unchanged.

```typescript src/main.ts
app.use(helmet());
app.use(compression());
```

## Module lifecycle hooks

Nest calls these as modules are initialised and destroyed:

```typescript
@Injectable()
export class PrismaService extends PrismaClient
  implements OnModuleInit, OnModuleDestroy {

  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}
```

| Hook | When |
|---|---|
| `onModuleInit` | once the host module's dependencies are resolved |
| `onApplicationBootstrap` | once every module has initialised |
| `onModuleDestroy` | when a termination signal is received |
| `beforeApplicationShutdown` | after all `onModuleDestroy` complete |
| `onApplicationShutdown` | last, with the signal |

Nest **awaits** async hooks, which is why they are the right place for setup that a constructor cannot do:

```typescript
@Injectable()
export class ReportService {
  constructor(private readonly prisma: PrismaService) {
    this.warmCache();          // WRONG — constructors cannot be async;
  }                            // this floats an unhandled promise
}

@Injectable()
export class ReportService implements OnModuleInit {
  async onModuleInit() {
    await this.warmCache();    // right — Nest waits for this
  }
}
```

## The one line people forget

```typescript src/main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableShutdownHooks();          // <- this one
  await app.listen(3000);
}
```

Without it, `onModuleDestroy` never runs. Database connections are not closed, in-flight requests are cut off mid-response, and every redeploy leaks connections until the pool is exhausted.

The symptom is nasty because it is gradual: the application works fine, then degrades over days as deploys accumulate, and nothing in the logs points at the cause. Two words in `main.ts` prevent it.

## Graceful shutdown, properly

```typescript
@Injectable()
export class QueueConsumer implements OnApplicationShutdown {
  private draining = false;

  async onApplicationShutdown(signal?: string) {
    this.draining = true;                    // stop accepting new work
    await this.waitForInFlight(30_000);      // let current jobs finish
  }
}
```

Combined with a readiness probe that fails as soon as `draining` is true, this is what lets a rolling deploy happen with no dropped requests.

## Exercise

```typescript
// 1. Write a middleware that starts an AsyncLocalStorage context holding
//    a requestId and the start time, applied to all routes except /health.
// 2. Write a service with OnModuleInit that connects to a cache, and
//    OnModuleDestroy that disconnects.
// 3. Say which line in main.ts makes step 2's cleanup actually run.
```

## Common questions

### Middleware or interceptor?

Middleware when you only need the raw request and do not care which route was matched — context setup, raw body, third-party Express plugins. Interceptor when you need route metadata or the response. If you find yourself wanting a decorator's value in middleware, it should be a guard or an interceptor.

### Why does my middleware not see the user from my auth guard?

Because middleware runs before guards. If you need authenticated context in middleware, that ordering cannot work — move the logic into a guard or an interceptor, or authenticate in the middleware itself.

### Do I need `enableShutdownHooks` in development?

You need it everywhere the process is ever terminated cleanly, which includes local development with hot reload. Leaving it out is how you end up with dozens of orphaned database connections while working.
