Express.js Mastery / Express: Letโ€™s Start Building The Natours API!

Param Middleware

๐Ÿ“– Context

This lecture introduces param middleware โ€” a special type of middleware that only runs when a specific parameter (like id) is present in the URL. In this app, the only relevant route parameter so far is id.


๐Ÿ”น Step 1: Creating a Basic Param Middleware

router.param('id', (req, res, next, val) => {
  console.log(`Tour id is ${val}`);
  next();
});

๐Ÿ’ก Why each part works:

  • ๐Ÿ›ฃ๏ธ router.param() โ€” a special method available on a router, used specifically to define param middleware.

  • ๐Ÿท๏ธ First argument ('id') โ€” the name of the parameter this middleware should watch for/run on.

  • ๐Ÿ“ฆ The middleware function still gets req, res, next โ€” but also a fourth argument, conventionally called val, which holds the actual value of that parameter from the URL (e.g. the specific tour ID requested).

  • โš ๏ธ next() โ€” still required at the end, otherwise the request-response cycle gets stuck and never reaches the actual route handlers further down the stack.


๐Ÿ”น Step 2: Testing Where This Middleware Runs

  • โŒ GET /api/v1/tours doesn't trigger this middleware because the route has no :id parameter.

  • โœ… It only runs for routes with an :id (e.g., GET /api/v1/tours/5).

  • Requesting a specific tour by ID โ†’ โœ… the log appears:

Tour id is 2
  • ๐Ÿ‘ค Requesting a user by ID โ†’ โŒ no log appears, even though the URL also has an id parameter.

๐Ÿ’ก Why it Doesn't Run for Users

  • ๐Ÿ›ฃ๏ธ This param middleware was defined only on the tour router.

  • ๐Ÿงฉ Since each router acts like its own mini sub-application, middleware defined on one router only becomes part of that router's middleware stack โ€” it has no effect on a different router (like the user router).

๐Ÿ”„ Request Flow Recap

A request to /tours/:id:

Request
   โฌ‡
๐ŸŒ App-level middleware
   โฌ‡
๐Ÿ›ฃ๏ธ Tour Router
   โฌ‡
๐Ÿ†” Param Middleware (id)
   โฌ‡
๐ŸŽฎ Route Handler
   โฌ‡
๐Ÿ“ค Response

๐Ÿ”น Step 3: A Practical Use Case โ€” Removing Repeated ID-Validation Code

โŒ The Problem

getTour, updateTour, and deleteTour all contain nearly identical code checking whether the given ID is valid, and returning an "Invalid ID" response if not.

๐Ÿ” This is repeated logic sitting in three different places โ€” not good practice.

โœ… The Solution

Move that validation logic into a single param middleware that runs automatically before any of these handlers, for any route containing an id.

exports.checkID = (req, res, next, val) => {
  if (req.params.id * 1 > tours.length) {
    return res.status(404).json({
      status: 'fail',
      message: 'Invalid ID'
    });
  }
  next();
};

๐Ÿ’ก Why the return Statement Is Critical

  • โŒ Without return, even after sending the 404 response, the function would continue executing and eventually still call next().

  • โžก๏ธ That would move the request into the next middleware, which would then try to send another response.

  • โš ๏ธ This triggers the classic Express error about not being allowed to set headers after a response has already been sent.

  • โœ… Adding return ensures the function exits immediately after sending the response, so next() is never reached in that case.


๐Ÿ”น Step 4: Wiring the Middleware into the Tour Router

  • Delete the repeated code from tourController.js file

  • And add console.log(Tour id is ${val}); :

  • Cut the selected part from tourRoutes.js and replace with this code:

router.param('id', tourController.checkID);
  • ๐Ÿ”— The duplicate ID-checking code is removed from:

    • ๐Ÿ“– getTour

    • โœ๏ธ updateTour

    • ๐Ÿ—‘๏ธ deleteTour

  • โœ… Those functions are now free to focus purely on their own job (getting, updating, or deleting a tour) without worrying about validation.


๐Ÿ”น Step 5: Verifying It Still Works

  • ๐Ÿ“– Request without an ID โ†’ โŒ no log, as expected.

  • โœ… Request with a valid ID โ†’ the Tour id is X log appears (proving checkID ran), and the actual tour handler still executes normally.

  • โŒ Request with an invalid ID โ†’ the middleware catches it and returns the 404 / "Invalid ID" response before the actual route handler ever runs.

  • ๐Ÿงช Tested on both GET and PATCH routes with an invalid ID โ€” both correctly return the error from the middleware.


๐Ÿ’ก Why This Approach Fits the Express Philosophy

  • ๐Ÿ”„ An alternative would be writing a plain reusable function and manually calling it inside each controller โ€” but that goes against how Express is designed to work.

  • โš™๏ธ Express favors building a middleware pipeline, where each piece of logic (like ID validation) lives in its own dedicated step in the stack.

โœ… Benefit

  • ๐ŸŽฏ Each controller function now only has one responsibility:

    • ๐Ÿ“– getTour โ†’ just gets a tour.

    • โœ๏ธ updateTour โ†’ just updates a tour.

    • ๐Ÿ—‘๏ธ deleteTour โ†’ just deletes a tour.

  • ๐Ÿ›ก๏ธ None of them need to worry about ID validation.

  • โž• If a new controller depending on id is added later, it automatically benefits from this validation without any extra code, since the param middleware runs for the whole router.


โœ… Key Takeaway

  • router.param() runs middleware only when the specified route parameter exists.

  • The callback receives the parameter value as the fourth argument (val).

  • Param middleware is scoped to its own router.

  • Use return when sending a response to prevent further execution.

  • Centralizing validation in param middleware keeps controllers clean and reusable.