# What is Middleware in Express and How It Works


Middleware is code that runs between receiving a request and sending a response. It's like a security checkpoint at an airport: every passenger passes through before boarding. Your request passes through middleware before reaching the route handler.

This is about understanding middleware and building better Express applications.

* * *

## What is Middleware in Express?

Middleware is a function that processes requests and responses.

### Simple Definition

Middleware is a function that:

1. Receives the request object
2. Does something with it
3. Either sends a response or passes control to the next middleware

### Real Example

When you visit a website:

```plaintext
User makes request
      |
      v
Middleware 1 checks if logged in
      |
      v
Middleware 2 validates the request
      |
      v
Middleware 3 logs the request
      |
      v
Route handler processes it
      |
      v
Response is sent
```

The request goes through several checkpoints before the actual route handler.

### The Checkpoint Analogy

Think of middleware like checkpoints at an airport:

```plaintext
Passenger enters airport
      |
      v
Checkpoint 1: Check ticket
      |
      v
Checkpoint 2: Security scan
      |
      v
Checkpoint 3: Passport check
      |
      v
Board the plane
```

Each checkpoint can:

- Allow you through (call `next()`)
- Stop you (send a response)
- Do something as you pass (check ticket, scan bag)

Express middleware works the same way.

### Basic Middleware Function

```javascript
function myMiddleware(req, res, next) {
  // req = incoming request
  // res = response to send
  // next = function to call to move to next middleware
  
  console.log("Request received");
  next();  // Pass to next middleware
}

app.use(myMiddleware);
```

All middleware functions have this signature: `(req, res, next)`.

* * *

## Where Middleware Sits in Request Lifecycle

### The Complete Request Flow

```plaintext
Client sends HTTP request
      |
      v
Request arrives at Express
      |
      v
Middleware 1 runs
      |
      v
Middleware 2 runs
      |
      v
Middleware 3 runs
      |
      v
Route handler runs
      |
      v
Response is created
      |
      v
Response sent to client
      |
      v
Client receives response
```

Middleware runs before the route handler. The route handler is the final step.

### Request Pipeline

Imagine a factory assembly line:

```plaintext
Raw materials (request)
      |
      v
Station 1: Clean (middleware 1)
      |
      v
Station 2: Paint (middleware 2)
      |
      v
Station 3: Inspect (middleware 3)
      |
      v
Station 4: Package (route handler)
      |
      v
Finished product (response)
```

Each station modifies or inspects. The final station packages everything.

### Code Example

```javascript
const express = require("express");
const app = express();

// Middleware 1
app.use((req, res, next) => {
  console.log("Middleware 1: Request started at", new Date());
  next();  // Move to next middleware
});

// Middleware 2
app.use((req, res, next) => {
  console.log("Middleware 2: Processing request");
  next();  // Move to next middleware
});

// Route handler
app.get("/", (req, res) => {
  console.log("Route handler: Sending response");
  res.send("Hello World");
});

app.listen(3000);
```

Order of execution:

```
Request to /
      |
      v
Middleware 1 runs: "Request started..."
      |
      v
Middleware 2 runs: "Processing request"
      |
      v
Route handler runs: "Sending response"
      |
      v
Response sent: "Hello World"
```

* * *

## Types of Middleware

### Type 1: Application-Level Middleware

Runs for every request to your application.

```javascript
// Applies to every request
app.use((req, res, next) => {
  console.log("This runs for every request");
  next();
});
```

### Type 2: Router-Level Middleware

Runs only for specific routes.

```javascript
// Applies only to /api routes
app.use("/api", (req, res, next) => {
  console.log("This only runs for /api routes");
  next();
});
```

### Type 3: Built-in Middleware

Express provides middleware for common tasks.

```javascript
// Parse JSON bodies
app.use(express.json());

// Parse form data
app.use(express.urlencoded({ extended: true }));

// Serve static files
app.use(express.static("public"));
```

### Type 4: Error-Handling Middleware

Catches errors from other middleware.

```javascript
// Must have 4 parameters (err, req, res, next)
app.use((err, req, res, next) => {
  console.log("Error occurred:", err.message);
  res.status(500).json({ error: "Something went wrong" });
});
```

Error handlers must be defined last.

* * *

## Application-Level Middleware

Runs for all requests.

### Basic Application Middleware

```javascript
app.use((req, res, next) => {
  console.log("Running for every request");
  next();
});

app.get("/", (req, res) => {
  res.send("Home");
});

app.get("/about", (req, res) => {
  res.send("About");
});
```

Both GET requests pass through the middleware.

### Multiple Application Middleware

```javascript
// Middleware 1
app.use((req, res, next) => {
  console.log("Middleware 1");
  next();
});

// Middleware 2
app.use((req, res, next) => {
  console.log("Middleware 2");
  next();
});

// Middleware 3
app.use((req, res, next) => {
  console.log("Middleware 3");
  next();
});

app.get("/", (req, res) => {
  console.log("Route handler");
  res.send("Done");
});
```

Execution order:

```
Middleware 1
Middleware 2
Middleware 3
Route handler
```

### Conditional Application Middleware

```javascript
// Only for GET requests
app.use((req, res, next) => {
  if (req.method === "GET") {
    console.log("This is a GET request");
  }
  next();
});

// Only for specific paths
app.use((req, res, next) => {
  if (req.path.startsWith("/admin")) {
    console.log("Admin route accessed");
  }
  next();
});
```

* * *

## Router-Level Middleware

Runs only for specific routes.

### Basic Router Middleware

```javascript
// Applies only to /admin routes
app.use("/admin", (req, res, next) => {
  console.log("Admin route accessed");
  next();
});

app.get("/admin/dashboard", (req, res) => {
  res.send("Admin Dashboard");
});

app.get("/admin/users", (req, res) => {
  res.send("User List");
});

// This route doesn't go through the middleware
app.get("/", (req, res) => {
  res.send("Home");
});
```

The middleware only runs for requests starting with `/admin`.

### Multiple Router Middleware

```javascript
// First middleware
app.use("/api", (req, res, next) => {
  console.log("API request received");
  next();
});

// Second middleware
app.use("/api", (req, res, next) => {
  console.log("Processing API request");
  next();
});

app.get("/api/users", (req, res) => {
  res.send("Users");
});
```

Both middlewares run for `/api/users` in order.

### Nested Routes with Middleware

```javascript
// Auth middleware only for protected routes
app.use("/api/admin", (req, res, next) => {
  console.log("Checking authentication");
  next();
});

app.get("/api/admin/stats", (req, res) => {
  res.send("Admin stats");
});

app.get("/api/public/posts", (req, res) => {
  res.send("Public posts");
});
```

`/api/admin/stats` passes through middleware. `/api/public/posts` doesn't.

* * *

## Built-in Middleware

Express provides common middleware.

### JSON Parser

```javascript
app.use(express.json());

app.post("/data", (req, res) => {
  console.log(req.body);  // Parsed JSON available
  res.send("Data received");
});
```

Automatically parses JSON request bodies.

### Form Data Parser

```javascript
app.use(express.urlencoded({ extended: true }));

app.post("/form", (req, res) => {
  console.log(req.body);  // Form data available
  res.send("Form received");
});
```

Parses form submissions.

### Static File Serving

```javascript
app.use(express.static("public"));

// Now http://localhost:3000/style.css serves public/style.css
// And http://localhost:3000/index.html serves public/index.html
```

Serves files from a directory.

### Static with Path Prefix

```javascript
app.use("/static", express.static("public"));

// Now http://localhost:3000/static/style.css serves public/style.css
```

Serve static files under a specific path.

* * *

## The Role of next()

`next()` is crucial. It tells Express to move to the next middleware.

### Without next()

```javascript
app.use((req, res, next) => {
  console.log("Middleware running");
  // Forgot to call next()
});

app.get("/", (req, res) => {
  res.send("Home");  // This never runs
});
```

The request hangs. The route handler never runs.

### With next()

```javascript
app.use((req, res, next) => {
  console.log("Middleware running");
  next();  // Pass to next middleware
});

app.get("/", (req, res) => {
  res.send("Home");  // Now this runs
});
```

The request moves forward. The route handler runs.

### Sending Response Instead of next()

```javascript
app.use((req, res, next) => {
  if (!isAuthenticated(req)) {
    res.status(401).json({ error: "Not authenticated" });
    return;  // Stop here, don't call next()
  }
  next();  // Continue if authenticated
});

app.get("/protected", (req, res) => {
  res.send("Protected content");
});
```

If authentication fails, send response. Otherwise, continue.

### next() with Errors

```javascript
app.use((req, res, next) => {
  try {
    // Some operation
    JSON.parse("invalid json");
    next();
  } catch (error) {
    next(error);  // Pass error to error handler
  }
});

// Error handler (4 parameters)
app.use((err, req, res, next) => {
  console.log("Error:", err.message);
  res.status(500).send("Error occurred");
});
```

Pass errors to error handlers with `next(error)`.

* * *

## Middleware Execution Order

Middleware runs in the order it's defined.

### Definition Order Matters

```javascript
// This runs first
app.use((req, res, next) => {
  console.log("First");
  next();
});

// This runs second
app.use((req, res, next) => {
  console.log("Second");
  next();
});

// This runs third
app.use((req, res, next) => {
  console.log("Third");
  next();
});

app.get("/", (req, res) => {
  console.log("Route handler");
  res.send("Done");
});
```

Output:

```
First
Second
Third
Route handler
```

### Changing Order

```javascript
app.use((req, res, next) => {
  console.log("A");
  next();
});

app.get("/", (req, res) => {
  res.send("Home");
});

app.use((req, res, next) => {
  console.log("B");  // This is defined AFTER the route
  next();
});
```

Output for GET /:

```
A
(No B - defined after route)
```

Middleware defined after a route won't run for that route.

### Route-Specific Order

```javascript
// Runs for all /admin routes
app.use("/admin", (req, res, next) => {
  console.log("Admin middleware");
  next();
});

app.get("/admin/users", (req, res) => {
  res.send("Users");
});

// Global middleware defined after still runs first
app.use((req, res, next) => {
  console.log("Global middleware");
  next();
});
```

Global middleware defined before route-specific middleware runs first.

* * *

## Request → Middleware → Route Handler Flow

```plaintext
HTTP Request arrives
      |
      v
Express receives request
      |
      v
Global middleware 1 executes
  (can access req, res)
  (can modify request)
  |
  ├─ Call next() → continue
  └─ Send response → stop here
      |
      v
Global middleware 2 executes
      |
      ├─ Call next() → continue
      └─ Send response → stop here
      |
      v
Route-specific middleware executes
      |
      ├─ Call next() → continue
      └─ Send response → stop here
      |
      v
Route handler executes
      |
      ├─ Send response → stop here
      └─ Call next() → (if defined)
      |
      v
Response sent to client
      |
      v
Client receives response
```

* * *

## Real-World Example: Logging Middleware

Log every request.

```javascript
const express = require("express");
const app = express();

// Logging middleware
app.use((req, res, next) => {
  const timestamp = new Date().toISOString();
  console.log(`[${timestamp}] ${req.method} ${req.path}`);
  next();
});

app.get("/", (req, res) => {
  res.send("Home");
});

app.get("/about", (req, res) => {
  res.send("About");
});

app.listen(3000, () => {
  console.log("Server running");
});
```

Output when accessing routes:

```
[2026-05-04T10:30:45.123Z] GET /
[2026-05-04T10:30:50.456Z] GET /about
```

### Enhanced Logging Middleware

```javascript
app.use((req, res, next) => {
  const start = Date.now();
  
  // Log when request ends
  res.on("finish", () => {
    const duration = Date.now() - start;
    console.log(
      `${req.method} ${req.path} - ${res.statusCode} - ${duration}ms`
    );
  });
  
  next();
});
```

Logs request duration too.

* * *

## Real-World Example: Authentication Middleware

Protect routes from unauthorized access.

```javascript
const express = require("express");
const app = express();

// Authentication middleware
function authMiddleware(req, res, next) {
  const token = req.headers.authorization;
  
  if (!token) {
    return res.status(401).json({ error: "No token provided" });
  }
  
  if (token === "Bearer valid-token-123") {
    req.user = { id: 1, name: "Alice" };
    next();
  } else {
    res.status(401).json({ error: "Invalid token" });
  }
}

// Public route
app.get("/", (req, res) => {
  res.send("Welcome");
});

// Protected route (use middleware)
app.get("/profile", authMiddleware, (req, res) => {
  res.send(`Hello ${req.user.name}`);
});

app.listen(3000);
```

Test without token:

```bash
curl http://localhost:3000/profile
# Error: No token provided
```

Test with token:

```bash
curl -H "Authorization: Bearer valid-token-123" http://localhost:3000/profile
# Hello Alice
```

### Protecting Multiple Routes

```javascript
// Apply middleware to all /api routes
app.use("/api", authMiddleware);

app.get("/api/users", (req, res) => {
  res.send("Users list");
});

app.get("/api/posts", (req, res) => {
  res.send("Posts list");
});

// This route is unprotected
app.get("/public", (req, res) => {
  res.send("Public data");
});
```

All `/api` routes require authentication. `/public` doesn't.

* * *

## Real-World Example: Request Validation Middleware

Validate request data before processing.

```javascript
const express = require("express");
const app = express();
app.use(express.json());

// Validation middleware
function validateEmail(req, res, next) {
  const email = req.body.email;
  
  if (!email) {
    return res.status(400).json({ error: "Email is required" });
  }
  
  if (!email.includes("@")) {
    return res.status(400).json({ error: "Invalid email format" });
  }
  
  next();
}

app.post("/signup", validateEmail, (req, res) => {
  const email = req.body.email;
  res.json({ message: `Signed up with ${email}` });
});

app.listen(3000);
```

Test with valid email:

```bash
curl -X POST http://localhost:3000/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com"}'
# Signed up with alice@example.com
```

Test with invalid email:

```bash
curl -X POST http://localhost:3000/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"invalid"}'
# Invalid email format
```

Test without email:

```bash
curl -X POST http://localhost:3000/signup \
  -H "Content-Type: application/json" \
  -d '{}'
# Email is required
```

### Multiple Validations

```javascript
function validateName(req, res, next) {
  if (!req.body.name || req.body.name.length < 2) {
    return res.status(400).json({ error: "Name must be at least 2 characters" });
  }
  next();
}

function validateEmail(req, res, next) {
  if (!req.body.email || !req.body.email.includes("@")) {
    return res.status(400).json({ error: "Invalid email" });
  }
  next();
}

app.post("/signup", validateName, validateEmail, (req, res) => {
  res.json({ message: "Signup successful" });
});
```

Both validations run before the route handler.

* * *

## Multiple Middleware Execution Chain

```plaintext
Request arrives
      |
      v
Middleware 1: Logging
  console.log("Request started")
  |
  ├─ next() called
  |
  v
Middleware 2: Authentication
  Check token
  |
  ├─ Token valid: next()
  ├─ Token invalid: send 401 error (stop)
  |
  v
Middleware 3: Request Validation
  Validate data
  |
  ├─ Data valid: next()
  ├─ Data invalid: send 400 error (stop)
  |
  v
Middleware 4: Add Timestamp
  req.timestamp = Date.now()
  |
  ├─ next() called
  |
  v
Route Handler
  Process request
  Send response
  |
  v
Response sent to client
```

Each middleware runs in order. If any middleware sends a response, the chain stops.

* * *

## Complete Practical Example

```javascript
const express = require("express");
const app = express();
app.use(express.json());

// Middleware 1: Logging
app.use((req, res, next) => {
  const timestamp = new Date().toISOString();
  console.log(`[${timestamp}] ${req.method} ${req.path}`);
  next();
});

// Middleware 2: Authentication
function authenticate(req, res, next) {
  const token = req.headers.authorization?.split(" ")[1];
  
  if (!token) {
    return res.status(401).json({ error: "No token" });
  }
  
  if (token === "valid-token") {
    req.user = { id: 1, name: "Alice" };
    next();
  } else {
    res.status(401).json({ error: "Invalid token" });
  }
}

// Middleware 3: Request validation
function validateRequest(req, res, next) {
  if (!req.body.email) {
    return res.status(400).json({ error: "Email required" });
  }
  
  if (!req.body.email.includes("@")) {
    return res.status(400).json({ error: "Invalid email" });
  }
  
  next();
}

// Public route (only logging)
app.get("/", (req, res) => {
  res.send("Home");
});

// Protected route (logging + authentication)
app.get("/profile", authenticate, (req, res) => {
  res.json({ message: `Hello ${req.user.name}` });
});

// Protected with validation (logging + authentication + validation)
app.post("/update-email", authenticate, validateRequest, (req, res) => {
  res.json({ message: `Email updated to ${req.body.email}` });
});

// Error handler
app.use((err, req, res, next) => {
  console.log("Error:", err.message);
  res.status(500).json({ error: "Server error" });
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});
```

Test:

```bash
# Public route (no authentication needed)
curl http://localhost:3000/
# Home

# Protected route without token
curl http://localhost:3000/profile
# No token

# Protected route with token
curl -H "Authorization: Bearer valid-token" http://localhost:3000/profile
# Hello Alice

# Update email without token
curl -X POST http://localhost:3000/update-email \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com"}'
# No token

# Update email with token
curl -X POST http://localhost:3000/update-email \
  -H "Authorization: Bearer valid-token" \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com"}'
# Email updated to alice@example.com
```

* * *

## Practice Assignment

**1. Create a logging middleware:**

```javascript
// Log every request with timestamp, method, and path
// Must call next() to continue
// Apply to all routes
```

**2. Build an authentication middleware:**

```javascript
// Check Authorization header for token
// If no token, send 401 error
// If valid token, set req.user and call next()
// Protect one route with it
```

**3. Create request validation middleware:**

```javascript
// Validate req.body.name (required, min 2 chars)
// Validate req.body.email (required, contains @)
// Return 400 error if invalid
// Use on a POST route
```

**4. Chain multiple middleware:**

```javascript
// Create logging → authentication → validation chain
// POST route should require all three
// GET route should only require logging
// Test both routes
```

**5. Create route-specific middleware:**

```javascript
// Create /api/admin routes protected by authentication
// Create /api/public routes without protection
// Show how same app has protected and public routes
```

* * *

## Quick Recap

- **Middleware** is code that runs between receiving a request and sending a response.

- Middleware sits in the request pipeline: request → middleware → handler → response.

- Every middleware function takes three parameters: `req`, `res`, and `next`.

- `next()` passes control to the next middleware in the chain.

- If middleware sends a response, the chain stops (don't call `next()`).

- **Application-level middleware** runs for all requests using `app.use()`.

- **Router-level middleware** runs only for specific paths using `app.use("/path", ...)`.

- **Built-in middleware** includes `express.json()`, `express.urlencoded()`, and `express.static()`.

- **Error-handling middleware** has four parameters: `(err, req, res, next)`.

- Middleware runs in the order it's defined.

- Middleware defined before a route affects that route.

- Middleware defined after a route doesn't affect that route.

- **Logging middleware** tracks all incoming requests.

- **Authentication middleware** protects routes from unauthorized access.

- **Validation middleware** checks request data before processing.

- Multiple middleware can be applied to a single route.

- Middleware can modify the request object (add properties like `req.user`).

- Use conditional logic in middleware to run code based on request properties.

Middleware is the foundation of building scalable Express applications.

* * *

> If you enjoyed this article, check out my other blogs on this profile. Connect with me: [LinkedIn](https://linkedin.com/in/rajharsh03) | [GitHub](https://github.com/RajHarsh03) | [X (Twitter)](https://x.com/rajharsh03)
