Skip to Content
πŸŽ‰ Welcome to my notes πŸŽ‰
Express.jsπŸ«€ Understanding Middleware in Depth

πŸ«€ Understanding Middleware in Depth

Middleware functions are the heart of any Express application. They are functions that sit in the middle of the request-response cycle, executing sequentially. Each middleware has access to the request object (req), the response object (res), and a callback function called next to pass control to the next middleware in the chain.

The best analogy is an international airport’s customs and security checkpoint. When you (the request) arrive, you go through a series of checkpoints:

  1. Passport Control (A logging middleware, like morgan, that logs your arrival).
  2. Security Scan (A security middleware, like helmet, that checks for dangerous headers).
  3. Baggage Check (A body-parser, like express.json(), that opens your luggage and makes the contents available).
  4. Final Destination Gate (The actual route handler, e.g., app.get('/flights', ...), that sends you on your way).

Each checkpoint can inspect you, add a stamp to your passport (req.user = ...), or deny you entry (res.status(401).send(...)). If everything is okay, they call next() to send you to the next checkpoint.

🧱 The Anatomy of Middleware

A standard middleware function has the signature (req, res, next).

  • req: The incoming request object. You can read data from it or add new properties to it.
  • res: The outgoing response object. You can use it to send a response back to the client, ending the cycle.
  • next: A function that, when called, passes control to the next middleware function in the stack.
    • next(): Proceeds to the next middleware.
    • next(error): Skips all remaining regular middleware and goes directly to the error-handling middleware.
    • next('route'): Skips the rest of the middleware functions in the current router and moves to the next route.

If a middleware function doesn’t call next() or send a response with res, the request will be left hanging, and the client will eventually time out.

πŸ—‚οΈ Types of Middleware

Express features several types of middleware, categorized by where they are defined.

🌍 1. Application-level Middleware

This is the most common type, bound to the entire application using app.use() or app.METHOD() (e.g., app.get()). It applies to all routes that are defined after it.

  • Use Cases: Logging requests, parsing bodies, setting security headers, authenticating all incoming requests.

🚦 2. Router-level Middleware

This works just like application-level middleware but is bound to an instance of express.Router(). It’s used to create modular, mountable route handlers.

  • Use Cases: Applying an authentication check only to routes within /api/v1/*, or validating an API key for a specific subset of endpoints.

πŸ›‘οΈ 3. Error-handling Middleware

This is a special type of middleware with a different signature: (err, req, res, next). It has four arguments instead of three. It’s defined at the very end of your middleware stack and only executes when an error is passed to next(err). As noted before, Express v5 automatically catches errors from async functions, making this pattern even more powerful.

  • Use Cases: Catching all server errors, formatting a consistent JSON error response, and logging error details.

πŸ”§ 4. Built-in Middleware

Starting from version 4.x, Express comes with its own set of essential middleware functions, which were previously separate modules.

  • express.json(): Parses incoming requests with JSON payloads.
  • express.urlencoded(): Parses incoming requests with URL-encoded payloads (form submissions).
  • express.static(): Serves static assets like HTML, CSS, and JavaScript files from a directory.

πŸ“¦ 5. Third-party Middleware

This is middleware from the vast npm ecosystem that adds specific functionality.

  • helmet: Helps secure your app by setting various HTTP headers.
  • cors: Enables Cross-Origin Resource Sharing.
  • morgan: An advanced HTTP request logger.

βœ… Key Takeaway: Order is Crucial

The single most important rule of middleware is that order matters. They are executed sequentially. A middleware defined at the top will always run before one defined at the bottom.

  1. Global middleware (like cors and helmet) should come first.
  2. Body parsers (express.json()) must come before any route handlers that need to access req.body.
  3. Your route definitions (app.use('/users', userRoutes)) come after general middleware.
  4. Error-handling middleware (app.use((err, req, res, next) => ...)) must be defined last.

MIDDLEWARE.md

6. Understanding Middleware πŸ”—

Middleware functions are the building blocks of an Express application. They are functions that execute during the lifecycle of a request to the server. Each middleware function has access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle.

Analogy: Think of an assembly line in a factory. The raw request (req) is the car chassis that comes in at the beginning. Each middleware is a station on the line.

  • Station 1 might be a logger that inspects the chassis and takes notes.
  • Station 2 might add the wheels (e.g., parse the request body).
  • Station 3 might be a security check that rejects the chassis if it’s faulty.

Each station, after doing its job, passes the car to the next station. The final station is the route handler, which paints the car and sends it out the door (res.send()).

The Anatomy of a Middleware Function

A middleware function is simply a function with a specific signature: (req, res, next).

  1. req: The request object.
  2. res: The response object.
  3. next: A function that, when invoked, executes the next middleware in the stack.

A middleware function can perform the following tasks:

  • Execute any code.
  • Make changes to the request and the response objects.
  • End the request-response cycle (by sending a response).
  • Call the next middleware in the stack by calling next().

⚠️ Important: If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging, and the client will time out.

How to Use Middleware

You apply middleware to your application using the app.use() method.

πŸ’» Code Example: A Simple Logger

This is the β€œHello World” of middleware. We’ll create a simple function that logs details about every incoming request before passing it along.

index.js
const express = require("express"); const app = express(); // 1. Define the middleware function const loggerMiddleware = (req, res, next) => { // Log the request method and the URL path console.log(`${req.method} ${req.path}`); // 2. Call next() to pass control to the next middleware/route handler next(); }; // 3. Apply the middleware to the application // This will run for EVERY incoming request. app.use(loggerMiddleware); // A simple route handler app.get("/", (req, res) => { res.send("Homepage"); }); app.get("/about", (req, res) => { res.send("About Page"); }); app.listen(3000, () => { console.log("Server is running on port 3000"); });
When you run this server and visit `http://localhost:3000/about`, your terminal will log `GET /about`. ### The Order of Middleware is Crucial Express executes middleware sequentially in the order it's defined in your code. The order matters! - A middleware function defined with `app.use()` will only run for requests that come in _after_ its definition. - Body-parsing middleware must come _before_ any routes that need to access `req.body`. - Authentication middleware must come _before_ the protected routes it's supposed to guard. --- ### ✨ Summary - Middleware functions are the core building blocks of an Express application, sitting between the request and the final route handler. - They have the signature **`(req, res, next)`**. - The **`next()`** function is the key to passing control down the middleware chain. - Middleware can execute code, modify `req` and `res`, or end the request cycle. - The **order** in which you define and apply middleware is extremely important. Of course. Let's explore the most common ways middleware is used in Express to build powerful and organized applications. --- # 7. Common Middleware Patterns & Use Cases 🧩 Middleware is used for a huge variety of tasks that are essential for any web application. This includes logging requests, serving static files like images and CSS, parsing incoming data, handling cookies, and authenticating users. Let's look at the most common patterns. ### Global vs. Route-Specific Middleware You can apply middleware in two main ways: globally to every request, or specifically to certain routes. 1. **Application-level (Global) Middleware**: This type of middleware is applied to the entire application using `app.use()`. It runs for **every single request** that your server receives, regardless of the path or HTTP method. - **Use Cases**: Logging, setting security headers, parsing request bodies, serving static files. 2. **Route-level (Route-Specific) Middleware**: This middleware is applied to a specific route or a group of routes. It runs **only** when a request matches that specific route. - **Use Cases**: Authentication (checking if a user is logged in before allowing access to `/dashboard`), validation (validating data for a `POST` request to `/users`). **πŸ’» Code Example** ```javascript filename="index.js" showLineNumbers const express = require("express"); const app = express(); // 1. Application-level (Global) middleware for logging app.use((req, res, next) => { console.log( `Global Logger: A request was made at ${new Date().toISOString()}`, ); next(); }); // 2. Route-level middleware for authentication const checkAuth = (req, res, next) => { // In a real app, you'd check for a valid session or token const isLoggedIn = true; // Simulating a logged-in user if (isLoggedIn) { next(); // User is authenticated, proceed to the route handler } else { res.status(401).send("Unauthorized"); } }; // Public route - does not use the checkAuth middleware app.get("/", (req, res) => { res.send("Welcome to the homepage!"); }); // Protected route - uses the checkAuth middleware app.get("/dashboard", checkAuth, (req, res) => { res.send("Welcome to your dashboard!"); }); app.listen(3000); ``` --- ### Serving Static Files with `express.static` Express has a built-in middleware function, **`express.static`**, for serving static files such as images, CSS, and JavaScript files. You point it to a directory, and Express will automatically serve any files from that directory. **πŸ’» Code Example** If you create a directory named public and put an index.html and styles.css file inside it: ```javascript filename="index.js" showLineNumbers // app.js const express = require("express"); const path = require("path"); const app = express(); // This middleware tells Express to serve static files from the 'public' directory app.use(express.static(path.join(__dirname, "public"))); app.listen(3000); ``` Now, if you go to `http://localhost:3000`, Express will serve `public/index.html`. If you go to `http://localhost:3000/styles.css`, it will serve `public/styles.css`. --- ### Parsing Request Bodies with `express.json` & `express.urlencoded` As we've seen, `req.body` is `undefined` by default. Express provides two crucial built-in middleware functions to parse incoming request bodies. - **`express.json()`**: Parses incoming JSON payloads. - **`express.urlencoded({ extended: true })`**: Parses payloads from HTML forms. You typically apply these globally at the top of your application. **πŸ’» Code Example** ```javascript filename="index.js" showLineNumbers const express = require("express"); const app = express(); // Global middleware to parse JSON and URL-encoded bodies app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.post("/api/users", (req, res) => { // Now req.body is populated and ready to use console.log(req.body); res.json({ message: "User created", data: req.body }); }); app.listen(3000); ``` --- ### Handling Cookies with Third-Party Middleware Express does not have a built-in way to handle cookies. For this, you use third-party middleware. The most popular one is **`cookie-parser`**. After installing and applying it with `app.use()`, it populates the `req.cookies` object with any cookies sent by the client. You can then use the `res.cookie()` method to set cookies on the client. **πŸ’» Code Example** ```javascript filename="index.js" showLineNumbers const express = require("express"); const cookieParser = require("cookie-parser"); const app = express(); // Apply the cookie-parser middleware app.use(cookieParser()); // A route to set a cookie app.get("/set-cookie", (req, res) => { res.cookie("username", "Alice", { maxAge: 900000, httpOnly: true }); res.send("Cookie has been set!"); }); // A route to read the cookie app.get("/read-cookie", (req, res) => { // The middleware populates req.cookies const username = req.cookies.username; res.send(`Welcome back, ${username}!`); }); app.listen(3000); ``` --- ### ✨ Summary - Middleware can be applied **globally** with `app.use()` or to **specific routes**. - Express has essential **built-in** middleware: - **`express.static`** for serving files. - **`express.json`** and **`express.urlencoded`** for parsing request bodies. - For specialized tasks like **cookies** or **file uploads**, you use **third-party** middleware like `cookie-parser` or `multer`.
Last updated on