The routes of the previous lesson say where a request goes. This lesson is about what happens on the way - because between a request entering the application and a controller method running, NestJS sets four posts: middleware, guards, interceptors and pipes. You will meet them in turn; we begin with the first.
Middleware is the toll post on the road. It stands furthest from the city, lets everyone through and writes each one into the register - merchant, courier, legionary. It does not ask where anyone is headed, because at the toll post that is not yet known.
Middleware must implement the
interface. Not NestMiddleware
CanActivate - that is the guards' interface from the next lesson; not NestInterceptor - those are interceptors; not ExceptionFilter - that one deals with errors. The interface calls for a single method, use, taking three arguments:1import { Injectable, NestMiddleware } from '@nestjs/common';
2import { Request, Response, NextFunction } from 'express';
3
4@Injectable()
5export class LoggerMiddleware implements NestMiddleware {
6 use(req: Request, res: Response, next: NextFunction) {
7 console.log(req.method, req.originalUrl);
8
9 next();
10 }
11}Three arguments, three roles.
(Request) carries everything that came from the client: the HTTP method, the address, the headers, the request body. req
(Response) lets you answer - set a status, a header, send content. res
(NextFunction) is the pass: calling next
next() hands the request onward.The middleware above writes the method and address to the console, then lets the request through. That much is enough to keep a register of everything entering the application.
Calling
next() is no formality. If middleware does not call next(), the request will hang - it will not proceed further in the chain. You will see no error and no exception: the server does not restart, the middleware does not execute twice, and the request does not pass to the controller on its own. The client simply waits until its timeout runs out.This is the commonest mistake in writing middleware, and the only one that leaves no trace in the logs.
Sometimes, though, we want to stop a request - and then one rule applies: since you are not passing it on, you must close the response yourself:
1@Injectable()
2export class TollMiddleware implements NestMiddleware {
3 use(req: Request, res: Response, next: NextFunction) {
4 if (!req.headers['x-toll-paid']) {
5 res.status(402).json({ message: 'Toll unpaid' });
6
7 return;
8 }
9
10 next();
11 }
12}Every path through the
use method must end either in a call to next() or in a response sent through res. There is no third possibility - or rather there is, and it is called a hung request.The class alone does nothing until you say where it should run. The process has four steps, in this order:
@Injectable().NestModule interface in the module class.consumer.apply(Middleware) in the configure() method..forRoutes().The first step is already behind you - it is the class from the previous example. The other three happen in a module:
1import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
2
3@Module({
4 controllers: [TributesController],
5 providers: [TributesService],
6})
7export class TributesModule implements NestModule {
8 configure(consumer: MiddlewareConsumer) {
9 consumer.apply(LoggerMiddleware).forRoutes('tributes');
10 }
11}implements NestModule is a promise that the class will supply a configure method - NestJS calls it while building the module. Inside, the consumer of type MiddlewareConsumer takes two pieces of information: .apply(LoggerMiddleware) says what to attach, .forRoutes('tributes') says where.The order of the steps matters in practice: a forgotten
implements NestModule leaves the configure method sitting there with nobody to call it, and the middleware never runs - again with no error at all..forRoutes() takes several forms. The string 'tributes' covers every route beginning with /tributes. A controller class, say TributesController, covers all of its routes. The object { path: 'tributes', method: RequestMethod.POST } narrows things to a single HTTP method. Routes can also be excluded with .exclude() - useful for /health, which there is no sense in logging.When middleware is to cover the whole application, you need no module:
1async function bootstrap() {
2 const app = await NestFactory.create(AppModule);
3
4 app.use(helmet());
5
6 await app.listen(3000);
7}app.use() in main.ts attaches middleware globally and accepts plain Express functions too - which is why ready-made libraries such as helmet and cors are switched on this way. There is one difference: middleware registered through app.use() does not go through dependency injection, so it has no access to the application's services.The register at the toll post, CORS headers, cookie parsing - all of these are independent of where a request is headed. And rightly so, because middleware runs at the Express level, before NestJS has settled which controller and which method will handle the request. It receives
req, res and next - three HTTP objects and nothing beyond them.Decisions of the kind "this endpoint requires a senator's rank" need knowledge the toll post does not have. That is what the second post is for, and it is the subject of the next lesson.
The toll post registers everyone and lets them through, @name:
NestMiddleware interface - not CanActivate, not NestInterceptor, not ExceptionFilter,use(req: Request, res: Response, next: NextFunction): req carries the request, res lets you answer, next hands it onward,next() the request hangs and will not proceed further in the chain - the server does not restart, the middleware does not run twice, the request does not reach the controller by itself,use ends either in next() or in a response sent through res,@Injectable() → implements NestModule in the module → consumer.apply(Middleware) in configure() → .forRoutes(),.forRoutes() takes a path, a controller class, or an object with path and method; .exclude() leaves chosen routes out,app.use() in main.ts attaches middleware globally, but without dependency injection,In the next lesson you will meet guards - the second post, the first to know where a request is headed and therefore able to say "no". For now remember one thing: middleware that fails to call
next() reports no error. It simply falls silent.