Skip to content
NestJS ns middleware 4 min read

Global Middleware

Some concerns are truly application-wide: you want to harden every response with security headers, parse every cookie, or log every request regardless of which module owns the route. For those cases NestJS lets you bypass the module system entirely and register middleware directly on the underlying platform instance with app.use() inside your bootstrap() function. This is the most global scope possible — it runs for every incoming request before Nest’s routing even resolves a handler — but it comes with one important trade-off: no dependency injection.

Registering middleware in bootstrap

The NestFactory.create() call returns an INestApplication, which exposes the platform’s own use() method. Anything you pass to app.use() is wired straight into the Express (or Fastify) middleware stack, exactly as if you had called express().use() yourself. This is where battle-tested community packages like helmet, cors, morgan, and cookie-parser belong, because they ship as plain (req, res, next) functions and need nothing from Nest’s container.

// main.ts
import { NestFactory } from '@nestjs/core';
import helmet from 'helmet';
import * as cookieParser from 'cookie-parser';
import morgan from 'morgan';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Runs for EVERY request, before Nest routing.
  app.use(helmet());
  app.use(cookieParser());
  app.use(morgan('tiny'));

  await app.listen(3000);
}
bootstrap();

Every request now flows through helmet -> cookieParser -> morgan before reaching any controller. The order of app.use() calls is the order of execution, just like in raw Express.

Output:

GET /cats 200 12 - 4.118 ms
POST /cats 201 27 - 9.502 ms

Enabling CORS

CORS is so common that Nest provides a dedicated helper, app.enableCors(), which configures the same underlying cors middleware globally. You can also call app.use(cors(options)) directly, but the helper is cleaner and the recommended path.

// main.ts (continued)
app.enableCors({
  origin: ['https://app.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true,
});

Register CORS and helmet as early as possible in bootstrap(). If a security or origin check runs after other logic, you risk leaking headers or processing requests you intended to reject.

Global vs module-scoped middleware

Global middleware (app.use) and module-scoped middleware (the configure(consumer) method of a NestModule) sit at the same point in the lifecycle, but differ sharply in capability and intent.

AspectGlobal (app.use)Module-scoped (MiddlewareConsumer)
Where registeredmain.ts bootstrapA module’s configure() method
Dependency injectionNoYes
Route targetingAll routes, alwaysforRoutes() / exclude() fine-grained
Best forhelmet, cors, morgan, body parsersApp logic needing services
FormFunctional middleware onlyFunctional or NestMiddleware class

The practical rule: use app.use() for third-party, platform-level packages that apply everywhere, and use a module’s configure() for anything that needs your own providers or selective route binding.

The dependency injection limitation

The single biggest constraint of app.use() is that it runs outside Nest’s DI container. The application context exists, but the middleware you register is a bare function — Nest never instantiates it, so it cannot inject a ConfigService, a repository, or any other provider. If you write a NestMiddleware class and try to pass it to app.use(), its constructor dependencies will simply be undefined.

// This does NOT work — `config` will be undefined at request time.
@Injectable()
export class TenantMiddleware implements NestMiddleware {
  constructor(private readonly config: ConfigService) {}
  use(req: Request, res: Response, next: NextFunction) {
    next();
  }
}

// main.ts — the class is never resolved through DI here:
app.use(new TenantMiddleware(/* no container available */));

When you genuinely need a provider inside globally-applied middleware, register it through a module instead and target every route:

// app.module.ts
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { TenantMiddleware } from './tenant.middleware';

@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    // Applies to every route, but is fully DI-aware.
    consumer.apply(TenantMiddleware).forRoutes('*');
  }
}

This gives you global reach with full dependency injection — the best of both worlds — at the cost of writing it in a module rather than main.ts.

Functional middleware registered with consumer.apply() also cannot inject providers. The deciding factor for DI is whether the middleware is a NestMiddleware class resolved by Nest, not whether it is global.

Best Practices

  • Reserve app.use() for third-party, platform-level packages (helmet, cors, cookie-parser, morgan) that need no application state.
  • Register security middleware (helmet, enableCors) first, before logging or parsing, so rejected requests never reach your code.
  • Prefer app.enableCors() over manually wiring the cors package — it is clearer and the supported Nest API.
  • Never expect dependency injection from app.use(); if you need a provider, use a module’s configure() with forRoutes('*').
  • Keep the app.use() ordering deliberate — execution follows registration order exactly, just like raw Express.
  • Avoid duplicating the same middleware both globally and per-module; pick one scope to keep the request path predictable.
Last updated June 14, 2026
Was this helpful?