Express.js Mastery / Express: Let’s Start Building The Natours API!

Creating Our Own Middleware

πŸ“– Context

Now that middleware and the request-response cycle concept have been covered, it's time to actually write custom middleware functions in code.


πŸ”„ Recap: Middleware Already Used

express.json() was already used earlier via app.use(). Calling express.json() returns a function, and that function gets added to the middleware stack. app.use() is the method used to add any middleware to the stack.


πŸ”Ή Step 1: Writing a Basic Custom Middleware Function

app.use((req, res, next) => {
  console.log('Hello from the middleware πŸ‘‹');
  next();
});

πŸ’‘ Why each part works:

  • 🧩 app.use() β€” registers this function as middleware, adding it to the middleware stack.

  • πŸ“¦ The function takes three arguments: req, res, and next. Having a third argument is what tells Express "this is middleware" β€” Express automatically passes the next function as the third argument.

  • 🏷️ Naming conventions (req, res, next) aren't mandatory β€” the arguments could technically be named anything (e.g. x, n) β€” but sticking to convention avoids confusion for anyone reading the code.

  • πŸ–¨οΈ console.log(...) β€” just some placeholder code to prove the middleware runs on each request.

  • ⚠️ next() β€” critical: without calling next(), the request-response cycle would get stuck at this point. The app would never move on to send a response back to the client. Forgetting to call next() is one of the most important mistakes to avoid in Express.


πŸ”Ή Step 2: Testing β€” Middleware Applies to Every Request

πŸ“€ Sending a request to any route (e.g. Get All Tours) β€” the console logs:

βœ… This confirms that this middleware (defined without a specific route) applies to every single request β€” but only if it's positioned before the relevant route handler in the code.


πŸ”Ή Step 3: Understanding Order β€” Moving the Middleware After a Route Handler

πŸ§ͺ Demonstration

The middleware function is moved to after the getAllTours route handler in the code.

πŸ“‹ Result

Sending a request to that same route β†’ Hello from the middleware πŸ‘‹ is not logged anymore.

πŸ’‘ Why this happens:

  • Route handlers are themselves a type of middleware β€” just middleware limited to a specific URL.

  • getAllTours calls res.json(...), which ends the request-response cycle right there.

  • Since the cycle has already finished by the time it would reach the custom middleware, that middleware is simply never called.

  • This proves that order matters fundamentally in Express β€” middleware placed after a route handler that sends a response will never run for that route.

βœ… Confirming with Another Route

πŸ“€ Sending a request to a different route (e.g. Get Tour, Update Tour, or Delete Tour) β€” Hello from the middleware πŸ‘‹ does appear, because for this route, the middleware still comes before its handler in the overall stack.

  • Send back the middleware to the previous place

⭐ Best Practice

Global middleware like this should generally be defined before all route handlers, so it consistently runs for every request.


πŸ”Ή Step 4: A Second Middleware β€” Manipulating the Request Object

This middleware adds a custom property to the request object: a timestamp of when the request occurred.

app.use((req, res, next) => {
  req.requestTime = new Date().toISOString();
  next();
});

πŸ’‘ Why it works

  • πŸ“¦ Uses the standard middleware signature: req, res, next.

  • 🏷️ req.requestTime = new Date().toISOString() adds a custom timestamp to the request object.

  • πŸ•’ .toISOString() converts the date into a readable string.

  • ⚠️ next() passes control to the next middleware or route handler.


πŸ”Ή Step 5: Using the Custom Request Property in a Route Handler

Inside getAllTours, the injected requestTime can now be logged or included in the response:

const getAllTours = (req, res) => {
  console.log(req.requestTime);

  res.status(200).json({
    status: 'success',
    requestedAt: req.requestTime,
    // ...existing response data
  });
};

πŸ§ͺ Testing Result

The response correctly includes requestedAt with the current date/time β€” proving the value came from the custom middleware, since it was never defined inside the route handler itself.


πŸ”‘ Key Takeaways

  • 🧩 Use app.use() to register middleware.

  • βš™οΈ Middleware functions use the (req, res, next) signature.

  • ⚠️ Always call next() to continue the request-response cycle.

  • πŸ“‹ Middleware executes in the order it's defined.

  • 🏷️ Middleware can modify the req object (e.g., add requestTime).

  • πŸ“ Place global middleware before your route handlers.