Login

Sign Up

Middlewares in Express
Daksh Dixit

Posted on Aug 12, 2025 | Backend

Middlewares in Express

Hey Devs!

Have you noticed that sometimes you can't access certain features without signing in? Or that some features are limited to admins only? Or how you get redirected to a different page after submitting a form? These all happen with the help of middleware.

Just like their name suggests, middlewares are programs that run in the middle of the request-response cycle. They sit between the initial request and the final route handler. They can be used to check authentication, log requests, handle errors, parse incoming data, and perform many other utility tasks.

Today, we're going to break down what middleware is in Express.js and how you can use it to make your applications more powerful and secure.

Table of Contents
What is Middleware, Really?
How Does Middleware Work? The next() function
Why Do We Need Middleware? (Use Cases)
Types of Middleware in Express
A Closer Look at Built-in Middleware
A Practical Example: Putting It All Together
How to Test Your Middleware (with Postman/ThunderClient)
Important Tips & Cautions

What is Middleware, Really?

At its core, a middleware in Express is just a JavaScript function. But it's a special kind of function that has access to three important objects:

  1. The Request object (req): An object containing information about the incoming HTTP request, such as the headers, URL, and any data sent in the body.

  2. The Response object (res): An object used to send back the desired HTTP response to the client.

  3. The next function: A function that, when called, passes control to the next middleware function in the stack.

Think of it like an assembly line in a factory. A product (the req) comes in and moves along a conveyor belt. At each station, a worker (the middleware) inspects or modifies the product. Once their job is done, they pass it to the next station (next()). The final station is the route handler, which packages the product and ships it out (the res).

How Does Middleware Work? The next() function

The real magic of middleware is the next() function. When a request hits your Express server, it's passed to the first middleware function that matches the route. That middleware can then do one of three things:

  1. Perform some code, make changes to the req or res objects, and then pass control to the next middleware by calling next().

  2. End the request-response cycle by sending a response back to the client (e.g., res.send(), res.json(), res.redirect()). If it does this, next() is not called.

  3. Call next() with an error, which bypasses all remaining regular middleware and goes straight to the error-handling middleware.

If a middleware function doesn't call next() or send a response, the request will be left hanging, and the client will eventually time out. This is a very common bug for beginners!

Here’s the basic structure of a middleware function:

const myCustomMiddleware = (req, res, next) => {
  // You can add properties to the request object
  req.requestTime = Date.now();
  console.log('A request was received!');
  
  // Pass control to the next middleware in the chain
  next(); 
};

Why Do We Need Middleware? (Use Cases)

Middleware is the backbone of any non-trivial Express application. It helps keep your code clean, modular, and organized by handling common tasks.

1. Logging Requests

You often want to log every incoming request for debugging or analytics. A logger middleware is perfect for this.

const loggerMiddleware = (req, res, next) => {
  console.log(`${req.method} ${req.path} at ${new Date().toISOString()}`);
  next(); // Pass control to the next handler
};

app.use(loggerMiddleware); // Apply this to all requests

2. Parsing Request Bodies

When a client sends data to your server (e.g., from an HTML form or a JSON payload), it's not immediately available in a usable format. You need middleware to parse it, which we'll cover in the built-in middleware section.

3. Authentication & Authorization

This is one of the most common use cases. You can protect certain routes so that only logged-in users (authentication) or users with specific roles like 'admin' (authorization) can access them.

const checkAuth = (req, res, next) => {
  if (req.session.isLoggedIn) {
    // User is logged in, proceed to the route handler
    next(); 
  } else {
    // User is not logged in, end the cycle and send an error
    res.status(401).send('Access Denied: Please log in.'); 
  }
};

// Protect a specific route
app.get('/dashboard', checkAuth, (req, res) => {
  res.send('Welcome to your dashboard!');
});

Types of Middleware in Express

Express categorizes middleware into a few types, though they all follow the same fundamental structure.

  • Application-level middleware: Bound to the app object using app.use() or app.METHOD(). It applies to all requests coming into the application.

  • Router-level middleware: Works just like application-level middleware, but it is bound to an instance of express.Router(). This is useful for grouping route handlers for a specific part of your site (e.g., /users, /products).

  • Built-in middleware: Middleware that comes packaged with Express.

  • Error-handling middleware: This middleware is special because it has four arguments: (err, req, res, next). You define it last, and it acts as a catch-all for any errors.

  • Third-party middleware: Middleware available on npm. Popular examples include helmet (for security), cors (for cross-origin requests), and morgan (for logging).

A Closer Look at Built-in Middleware

Express comes with powerful middleware functions that you'll use in almost every project.

1. express.json()

This middleware parses incoming requests with JSON payloads. It looks at the Content-Type header and, if it's application/json, it will parse the body of the request into a JavaScript object and attach it to req.body.

// Without this, req.body would be undefined for JSON requests
app.use(express.json());

app.post('/api/users', (req, res) => {
  // Now you can access the JSON data sent by the client
  const newUser = req.body; 
  console.log(newUser); // e.g., { "name": "Ravi", "city": "Delhi" }
  res.status(201).json(newUser);
});

2. express.urlencoded()

This middleware is for parsing incoming requests from HTML forms (with Content-Type: application/x-www-form-urlencoded). It also attaches the parsed data to req.body.

// This allows you to read data from a standard HTML form submission
app.use(express.urlencoded({ extended: true }));

app.post('/login', (req, res) => {
  const username = req.body.username;
  const password = req.body.password;
  // ... process login
  res.send(`Welcome, ${username}!`);
});

3. express.static()

This middleware is used to serve static files like images, CSS files, and JavaScript files. You just need to tell it the name of the directory where your static assets are stored.

// Create a folder named 'public' in your project root
// Place files like 'style.css' or 'logo.png' inside it
app.use(express.static('public'));

// Now, if a request comes for /style.css, Express will automatically
// serve the file located at public/style.css

A Practical Example: Putting It All Together

Let's see how these pieces fit into a small Express server.

// server.js
const express = require('express');
const app = express();
const port = 3000;

// 1. Application-level middleware: Logger
const logger = (req, res, next) => {
  console.log(`[${new Date().toLocaleTimeString()}] Received ${req.method} request for ${req.url}`);
  next();
};
app.use(logger);

// 2. Built-in middleware: To parse JSON bodies
app.use(express.json());

// 3. Custom middleware: Simple "Admin Only" check
const isAdmin = (req, res, next) => {
  // In a real app, you'd check a session or JWT, not the body
  const { role } = req.body; 
  if (role === 'admin') {
    console.log('Admin access granted.');
    next();
  } else {
    res.status(403).send('Forbidden: Admins only.');
  }
};

// Public route
app.get('/', (req, res) => {
  res.send('Welcome to the homepage!');
});

// Protected route that uses the 'isAdmin' middleware
app.post('/admin/data', isAdmin, (req, res) => {
  res.json({ message: 'Here is the secret admin data.' });
});

// 4. Error-handling middleware (defined last)
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

How to Test Your Middleware (with Postman/ThunderClient)

You can't test POST requests or custom headers easily in a browser. This is where API testing tools like Postman or the VS Code extension ThunderClient are essential.

Here’s how you would test the /admin/data route from our example:

Step 1: Create a New Request Open Postman or ThunderClient and create a new request.

Step 2: Set the Method and URL

  • Change the HTTP method from GET to POST.

  • Enter the URL: http://localhost:3000/admin/data.

Step 3: Add the Request Body (The Important Part!)

  • Go to the "Body" tab.

  • Select the "raw" option and choose "JSON" from the dropdown menu.

  • This tells the tool to send a JSON payload and set the Content-Type header to application/json for you.

Scenario A: Testing the "Failure" Case In the text area for the body, enter a JSON object without the admin role:

{
    "role": "user"
}

Now, hit "Send". You should get back a 403 Forbidden response with the message "Forbidden: Admins only." This proves your isAdmin middleware is correctly blocking non-admins.

Scenario B: Testing the "Success" Case Now, change the body to include the correct role:

{
    "role": "admin"
}

Hit "Send" again. This time, you should get a 200 OK response with the JSON message: {"message": "Here is the secret admin data."}. This proves your middleware is allowing admins to pass through to the route handler.

Important Tips & Cautions

  • Order is Crucial: Middleware is executed in the order it is defined. Always define global middleware (like loggers and body parsers) at the top of your file.

  • Don't Forget next(): If your middleware doesn't send a response, it must call next() to avoid leaving the request hanging.

  • Error Middleware Goes Last: Your custom error-handling middleware (the one with four arguments) must be defined after all other app.use() calls and routes.

  • Be Specific: When possible, apply middleware to specific routes or routers instead of globally. This prevents unnecessary code from running on every single request.

Conclusion

Middleware is a simple but incredibly powerful concept that forms the architectural foundation of Express.js applications. By chaining together these small, focused functions, you can handle complex tasks like authentication, logging, and data processing in a clean, reusable, and scalable way. Mastering middleware is a key step toward becoming a proficient back-end developer.

Have fun building with Express!

Enjoy the content here?

Sign up on our platform or join our WhatsApp channel here to get more hands-on guides like this, delivered regularly.

See you in the next blog. Until then, keep practicing and happy learning!

4 Reactions

0 Bookmarks

Read next

Daksh Dixit

Daksh Dixit

Dec 29, 24

14 min read

|

Decoding the Web: A Beginner's Guide

Daksh Dixit

Daksh Dixit

Jan 5, 25

10 min read

|

Introduction to HTML, CSS, and JavaScript