In Express.js, middlewares are special functions that have access to the request (req), response (res), and a third parameter called next. Unlike regular route handlers, middlewares play a critical role in controlling the flow of the application by executing external logic before the main business logic.
How Do Middlewares Work?
When an HTTP request hits an Express.js server, it flows through a series of middleware functions. Each middleware can:
- Modify the request object (e.g., attach data, validate tokens).
- Modify the response object (e.g., send a response early).
- Pass control to the next middleware in the stack using the next() function.
If a middleware doesn't call next(), the request-response cycle terminates there, and no further logic (including route handlers) will execute.
Why Do We Use Middlewares?
Middlewares are perfect for scenarios where we need to add reusable logic before processing a request. For instance:
- Authentication: Checking if the user is logged in (e.g., validating - JWT tokens).
- Authorization: Ensuring users have the necessary permissions to perform certain actions (e.g., admins can delete content).
- Request Validation: Verifying if all required inputs are provided.
- Logging and Monitoring: Recording details of incoming requests for analytics or debugging.
- Error Handling: Catching errors globally to send meaningful responses.
Defining and Using Middlewares
A middleware function looks like this:
app.use((req, res, next) => {
// Logic here
next(); // Pass control to the next middleware or route handler
});
- req (Request): Contains information about the incoming HTTP request (e.g., headers, body, params).
- res (Response): Used to send data back to the client. next(): A function that passes control to the next middleware in line.
Middleware Flow: Order of Execution
Middleware order matters! Express executes middlewares sequentially in the order they are defined.
If a middleware is defined after a route, it will not affect that route. This is why middlewares must be declared before routes in your app.js.
Example:
// Middleware to check if the user has admin privileges
app.use((req, res, next) => {
console.log("Checking for admin role...");
// Simulating a user object attached earlier in the pipeline
if (req.user && req.user.role === "admin") {
console.log("Access granted");
next(); // Move to the next middleware or route handler
} else {
console.log("Access denied");
res.status(403).send("You do not have access to this resource.");
}
});
// Routes
app.get("/admin/dashboard", (req, res) => {
res.send("Welcome to the admin dashboard!");
});
app.get("/public", (req, res) => {
res.send("This is a public page.");
});
Internal Execution Flow
Here’s what happens step by step:
- Incoming Request: A request hits the server.
-
Middleware Execution:
- The middleware checks the req.user.role.
- If role is "admin", it calls next() to pass control to the next middleware or route.
- If not, the middleware terminates the request by sending a 403 Forbidden response.
- Route Handler: If next() is called, the relevant route handler (e.g., /admin/dashboard) executes.
Example Flow:
- A user with role: "admin" requests /admin/dashboard.
- Middleware logs "Access granted" and calls next().
- Route handler sends "Welcome to the admin dashboard!".
- A user with role: "user" requests /admin/dashboard.
- Middleware logs "Access denied" and sends "You do not have access to this resource".
Key Takeaways
- Middlewares are like gatekeepers—they decide what happens before the main route logic runs.
- Use next() to ensure the flow continues to the next middleware or route.
- Always define critical middlewares before the routes to ensure they apply.
- If you don’t call next() or send a response, the request will hang (timed out).
Top comments (0)