Bird
Raised Fist0
NestJSframework~15 mins

Functional middleware in NestJS - Deep Dive

Choose your learning style10 modes available
Overview - Functional middleware
What is it?
Functional middleware in NestJS is a simple function that runs during the request-response cycle. It can read or modify the request and response objects before passing control to the next function. This helps add features like logging, authentication, or error handling without changing the main code.
Why it matters
Without middleware, every route handler would need to repeat common tasks like checking user login or logging requests. Functional middleware lets you write these tasks once and reuse them everywhere, making your code cleaner and easier to maintain. It also helps keep your app organized and scalable.
Where it fits
Before learning functional middleware, you should understand basic NestJS concepts like modules, controllers, and providers. After mastering middleware, you can explore advanced topics like interceptors, guards, and exception filters to control request flow and security.
Mental Model
Core Idea
Functional middleware is a simple function that intercepts requests to add or change behavior before reaching the main handler.
Think of it like...
It's like a security guard at a building entrance who checks visitors before letting them inside. The guard can stop, allow, or modify the visitor's path based on rules.
┌───────────────┐   request   ┌───────────────┐   next()   ┌───────────────┐
│ Client       │────────────▶│ Middleware    │───────────▶│ Controller    │
└───────────────┘            └───────────────┘           └───────────────┘

Middleware can:
- Read or change request
- Perform tasks (e.g., logging)
- Call next() to continue
- Or end response early
Build-Up - 7 Steps
1
FoundationUnderstanding Middleware Basics
🤔
Concept: Middleware is a function that runs between receiving a request and sending a response.
In NestJS, middleware functions take three arguments: request, response, and next. They can read or modify the request or response objects. Calling next() passes control to the next middleware or route handler.
Result
You can add code that runs for every request, like logging or checking headers.
Understanding middleware as a simple function clarifies how you can insert custom logic anywhere in the request flow.
2
FoundationCreating a Functional Middleware
🤔
Concept: Functional middleware is a plain function, not a class, that fits NestJS middleware signature.
Example: function logger(req, res, next) { console.log(`Request to ${req.url}`); next(); } This logs every request URL and then continues processing.
Result
Every request URL is printed in the console before the route handler runs.
Knowing that middleware can be simple functions makes it easy to write and test small reusable pieces.
3
IntermediateApplying Middleware in NestJS Modules
🤔
Concept: Middleware functions are applied in modules using configure() method with MiddlewareConsumer.
In your module: import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common'; @Module({}) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(logger) .forRoutes('*'); } } This applies logger middleware to all routes.
Result
All incoming requests pass through the logger middleware before reaching controllers.
Understanding how to connect middleware to routes lets you control where and when your middleware runs.
4
IntermediateControlling Middleware Execution Flow
🤔Before reading on: Do you think middleware can stop a request from reaching the controller? Commit to yes or no.
Concept: Middleware can decide to end the response early or pass control to the next handler by calling next().
Example: function auth(req, res, next) { if (!req.headers.authorization) { return res.status(401).send('Unauthorized'); } next(); } If no authorization header, response ends with 401. Otherwise, next() continues.
Result
Unauthorized requests get blocked early; authorized requests proceed normally.
Knowing middleware can short-circuit requests helps build security and validation layers efficiently.
5
IntermediateUsing Middleware with Route Filters
🤔Before reading on: Can you apply middleware only to specific routes or HTTP methods? Commit to yes or no.
Concept: Middleware can be applied selectively to routes or HTTP methods using forRoutes() with path and method filters.
Example: consumer .apply(logger) .forRoutes({ path: 'users', method: RequestMethod.GET }); This applies logger only to GET /users requests.
Result
Only requests matching the filter run the middleware; others skip it.
Selective middleware application improves performance and keeps unrelated routes clean.
6
AdvancedCombining Multiple Middleware Functions
🤔Before reading on: Do you think multiple middleware run in the order they are applied? Commit to yes or no.
Concept: You can chain multiple middleware functions; they run in the order applied, each calling next() to continue.
Example: consumer .apply(auth, logger) .forRoutes('profile'); First auth runs, then logger, then controller.
Result
Middleware run sequentially, allowing layered processing like auth then logging.
Understanding middleware order prevents bugs where middleware runs too early or late.
7
ExpertMiddleware vs Interceptors and Guards
🤔Before reading on: Are middleware, guards, and interceptors interchangeable in NestJS? Commit to yes or no.
Concept: Middleware runs before route handlers and can modify requests; guards control access; interceptors transform responses or handle extra logic.
Middleware is low-level and works with raw requests. Guards check permissions and can block requests. Interceptors wrap around handlers to modify output or handle errors.
Result
Each has a distinct role; using them correctly leads to clean, maintainable code.
Knowing the differences helps choose the right tool for security, logging, or response shaping.
Under the Hood
NestJS middleware functions are standard Express middleware under the hood. When a request arrives, NestJS passes it through the middleware chain before reaching controllers. Each middleware receives the request and response objects and a next function. Calling next() passes control to the next middleware or handler. Middleware can modify request or response objects directly, affecting downstream processing.
Why designed this way?
NestJS builds on Express to leverage its mature middleware system, allowing developers to use familiar patterns. Functional middleware keeps things simple and flexible, avoiding the overhead of classes when not needed. This design balances ease of use with powerful control over request flow.
┌───────────────┐
│ Incoming Req  │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Middleware 1  │
└──────┬────────┘
       │ next()
       ▼
┌───────────────┐
│ Middleware 2  │
└──────┬────────┘
       │ next()
       ▼
┌───────────────┐
│ Controller    │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Response Sent │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does middleware always run after guards? Commit to yes or no.
Common Belief:Middleware runs after guards and interceptors in NestJS.
Tap to reveal reality
Reality:Middleware runs before guards and interceptors because it is part of the underlying Express pipeline.
Why it matters:Misunderstanding this order can cause security checks to be bypassed or logging to miss early request data.
Quick: Can middleware modify the response after the controller sends it? Commit to yes or no.
Common Belief:Middleware can change the response even after the controller sends it.
Tap to reveal reality
Reality:Middleware runs before the controller sends the response, so it cannot modify the response after sending.
Why it matters:Expecting middleware to alter responses post-controller leads to bugs; interceptors are the correct tool for response transformation.
Quick: Is functional middleware always stateless? Commit to yes or no.
Common Belief:Functional middleware cannot hold or share state between requests.
Tap to reveal reality
Reality:Functional middleware can hold state using closures or external variables, but this must be done carefully to avoid bugs.
Why it matters:Assuming statelessness may cause unexpected behavior when middleware shares data across requests unintentionally.
Quick: Does applying middleware to '*' route mean it runs for all HTTP methods? Commit to yes or no.
Common Belief:Applying middleware to '*' runs it for all routes and all HTTP methods.
Tap to reveal reality
Reality:Applying to '*' runs middleware for all routes but only for HTTP methods supported by Express; some methods may be excluded.
Why it matters:Assuming full coverage can cause missing middleware on some HTTP methods, leading to security or logging gaps.
Expert Zone
1
Middleware functions can be asynchronous by returning promises or using async/await, but forgetting to await next() can cause subtle bugs.
2
Middleware order matters deeply; reversing order can break authentication or logging flows in production.
3
Functional middleware can be composed or wrapped to create reusable pipelines, enabling modular design patterns.
When NOT to use
Avoid using functional middleware for tasks that require access to NestJS dependency injection or lifecycle hooks; use class-based middleware or guards instead. For response transformation, prefer interceptors. For authorization logic, guards are more appropriate.
Production Patterns
In production, middleware is often layered: authentication middleware runs first, followed by logging and request parsing middleware. Middleware is selectively applied to routes to optimize performance. Error-handling middleware is added last to catch and format errors globally.
Connections
Express middleware
Functional middleware in NestJS is built on top of Express middleware.
Understanding Express middleware helps grasp NestJS middleware behavior and lifecycle.
HTTP request lifecycle
Middleware operates during the HTTP request lifecycle before the controller handles the request.
Knowing the HTTP lifecycle clarifies when and why middleware runs.
Assembly line manufacturing
Middleware processing is like an assembly line where each station adds or checks something before passing the product along.
This connection helps understand how middleware layers build up complex request handling step-by-step.
Common Pitfalls
#1Middleware does not call next(), causing requests to hang.
Wrong approach:function logger(req, res, next) { console.log('Request received'); // forgot to call next() }
Correct approach:function logger(req, res, next) { console.log('Request received'); next(); }
Root cause:Forgetting to call next() stops the request chain, so the server never responds.
#2Applying middleware globally but expecting it to run only on some routes.
Wrong approach:consumer.apply(auth).forRoutes('*'); // expects auth only on /users
Correct approach:consumer.apply(auth).forRoutes('users'); // applies only to /users route
Root cause:Misunderstanding forRoutes() filters leads to middleware running too broadly or narrowly.
#3Trying to use dependency injection inside functional middleware directly.
Wrong approach:function myMiddleware(req, res, next) { const service = this.myService; // undefined next(); }
Correct approach:Use class-based middleware with constructor injection or pass dependencies when creating functional middleware.
Root cause:Functional middleware lacks NestJS context, so DI is unavailable without extra setup.
Key Takeaways
Functional middleware in NestJS is a simple function that runs before route handlers to process requests and responses.
Middleware can modify requests, block unauthorized access, or add logging, improving code reuse and organization.
Middleware runs before guards and interceptors, so understanding their order is crucial for security and behavior.
Applying middleware selectively to routes and methods optimizes performance and clarity.
Knowing when to use middleware versus guards or interceptors helps build clean, maintainable NestJS applications.

Practice

(1/5)
1. What is the main purpose of functional middleware in NestJS?
easy
A. To run code before the route handler to modify requests or add features
B. To define database models for the application
C. To create UI components for the frontend
D. To compile TypeScript code into JavaScript

Solution

  1. Step 1: Understand middleware role

    Middleware runs before route handlers to process requests or add features like logging or authentication.
  2. Step 2: Identify correct purpose

    Options A, B, and D relate to other parts of development, not middleware.
  3. Final Answer:

    To run code before the route handler to modify requests or add features -> Option A
  4. Quick Check:

    Middleware runs before routes = C [OK]
Hint: Middleware runs before routes to add features [OK]
Common Mistakes:
  • Confusing middleware with database models
  • Thinking middleware creates UI components
  • Believing middleware compiles code
2. Which of the following is the correct way to define a functional middleware in NestJS?
easy
A. function logger(req, res) { console.log(req.url); }
B. class Logger { log(req, res) { console.log(req.url); } }
C. const logger = () => { console.log('log'); }
D. function logger(req, res, next) { console.log(req.url); next(); }

Solution

  1. Step 1: Check middleware signature

    Functional middleware must accept (req, res, next) and call next() to continue.
  2. Step 2: Validate options

    function logger(req, res, next) { console.log(req.url); next(); } matches this pattern. class Logger { log(req, res) { console.log(req.url); } } is a class, not functional middleware. const logger = () => { console.log('log'); } lacks parameters and next call. function logger(req, res) { console.log(req.url); } misses next parameter and call.
  3. Final Answer:

    function logger(req, res, next) { console.log(req.url); next(); } -> Option D
  4. Quick Check:

    Middleware needs (req, res, next) and calls next() [OK]
Hint: Middleware needs (req, res, next) and calls next() [OK]
Common Mistakes:
  • Omitting the next parameter
  • Not calling next() inside middleware
  • Using class instead of function for functional middleware
3. Given this middleware code, what will be logged when a request to '/home' is made?
function logger(req, res, next) {
  console.log('Request URL:', req.url);
  next();
}
medium
A. Request URL: undefined
B. Request URL: /home
C. No output because next() stops logging
D. Error: req.url is not defined

Solution

  1. Step 1: Understand req.url property

    In middleware, req.url contains the path of the incoming request, here '/home'.
  2. Step 2: Analyze console.log output

    The middleware logs 'Request URL:' followed by req.url, so it prints 'Request URL: /home'.
  3. Final Answer:

    Request URL: /home -> Option B
  4. Quick Check:

    req.url logs request path = A [OK]
Hint: req.url holds the request path string [OK]
Common Mistakes:
  • Assuming next() stops console.log
  • Thinking req.url is undefined
  • Confusing middleware with route handler output
4. Identify the error in this functional middleware code:
function auth(req, res, next) {
  if (!req.headers.authorization) {
    res.status(401).send('Unauthorized');
  }
  next();
}
medium
A. res.status should be res.sendStatus
B. Missing req parameter in function signature
C. next() is called even after sending response, causing errors
D. Authorization header check is incorrect

Solution

  1. Step 1: Analyze flow after sending response

    If authorization header is missing, response is sent with 401, but next() is still called, which can cause errors by continuing the request cycle.
  2. Step 2: Identify correct fix

    To fix, add return before res.status(401).send(...) to stop execution or call next() only if authorized.
  3. Final Answer:

    next() is called even after sending response, causing errors -> Option C
  4. Quick Check:

    Call next() only if no response sent [OK]
Hint: Don't call next() after sending response [OK]
Common Mistakes:
  • Calling next() after res.send causes errors
  • Forgetting to stop middleware after response
  • Misusing res.status vs res.sendStatus
5. You want to create a functional middleware that adds a custom header 'X-App-Version' with value '1.0' to every response. Which code correctly implements this?
hard
A. function addVersion(req, res, next) { res.setHeader('X-App-Version', '1.0'); next(); }
B. function addVersion(req, res) { res.setHeader('X-App-Version', '1.0'); }
C. function addVersion(req, res, next) { next(); res.setHeader('X-App-Version', '1.0'); }
D. function addVersion(req, res, next) { req.setHeader('X-App-Version', '1.0'); next(); }

Solution

  1. Step 1: Confirm middleware signature and header setting

    Middleware must accept (req, res, next) and call next() after setting headers. res.setHeader is correct method.
  2. Step 2: Evaluate options

    function addVersion(req, res, next) { res.setHeader('X-App-Version', '1.0'); next(); } sets header then calls next() correctly. function addVersion(req, res) { res.setHeader('X-App-Version', '1.0'); } misses next parameter and call. function addVersion(req, res, next) { next(); res.setHeader('X-App-Version', '1.0'); } calls next() before setting header, so header may not be sent. function addVersion(req, res, next) { req.setHeader('X-App-Version', '1.0'); next(); } uses req.setHeader which is incorrect because req cannot set response headers.
  3. Final Answer:

    function addVersion(req, res, next) { res.setHeader('X-App-Version', '1.0'); next(); } -> Option A
  4. Quick Check:

    Set header before next() with correct method [OK]
Hint: Set header before next() using res.setHeader [OK]
Common Mistakes:
  • Calling next() before setting headers
  • Omitting next parameter or call
  • Confusing req and res when setting headers
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard