Lesson 34 of 50 – Middleware in Express.js
68% Completed
68%

Middleware in Express.js

Middleware functions are used to process requests, execute common tasks, modify request or response objects, and control how requests move through an Express.js application.

In this lesson: Learn middleware syntax, application-level middleware, route-level middleware, built-in middleware, custom middleware, error-handling middleware, and middleware execution order.

1. What Is Middleware?

Middleware is a function that runs during the request-response cycle. It can execute code, read or modify request information, end the response, or pass control to another middleware function.

For example, middleware can be used to:

  • Log incoming requests.
  • Check whether a user is authenticated.
  • Read JSON data sent by a client.
  • Validate request information.
  • Handle errors.
  • Measure request processing time.
Request Processing Flow
Client Request
↓
Middleware 1

Log request details

↓
Middleware 2

Check request or user

↓
Route Handler

Process the requested URL

↓
Response to Client

2. Middleware Syntax

A basic middleware function looks like this:

function myMiddleware(req, res, next) {
    console.log('Middleware executed');

    next();
}

Explanation

  • req contains information about the incoming request.
  • res is used to send a response.
  • next() passes control to the next middleware or route handler.
Important: A middleware function should either end the response or pass control onward when appropriate. If it does neither, the request may remain pending.

3. Create Your First Middleware

Create a file named app.js and add the following code.

app.js
const express = require('express');
const app = express();

app.use((req, res, next) => {
    console.log('Middleware is running');

    next();
});

app.get('/', (req, res) => {
    res.send('Welcome to Express.js');
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
});

Run the application:

node app.js

Open http://localhost:3000.

Terminal output after a request:

Server running on port 3000
Middleware is running

The middleware runs before the matching route handler. Additional browser requests may produce additional log messages.

4. Application-Level Middleware

Application-level middleware is registered on the Express application using app.use() or an HTTP-method route method such as app.get().

Example: Log Every Request
const express = require('express');
const app = express();

app.use((req, res, next) => {
    console.log(
        req.method + ' ' + req.url
    );

    next();
});

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

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

app.listen(3000, () => {
    console.log('Server started');
});

When you visit /about, the terminal logs:

GET /about

This is useful for basic request logging and debugging.

5. Route-Level Middleware

Route-level middleware is applied to a particular route instead of every request handled by the application.

Example: Check Access to a Route
const express = require('express');
const app = express();

function checkAccess(req, res, next) {
    console.log('Checking access');

    next();
}

app.get('/public', (req, res) => {
    res.send('Public Page');
});

app.get('/dashboard', checkAccess, (req, res) => {
    res.send('Dashboard Page');
});

app.listen(3000, () => {
    console.log('Server started');
});

Visiting /dashboard runs checkAccess() before the route handler. Visiting /public does not run this middleware.

This example only demonstrates middleware placement. Logging a message does not provide real authentication or security.

6. Middleware That Stops a Request

Middleware does not always need to call next(). It can send a response and stop further processing.

Example: Block a Request
const express = require('express');
const app = express();

function blockRequest(req, res, next) {
    res.status(403).send('Access Denied');
}

app.get('/restricted', blockRequest, (req, res) => {
    res.send('Restricted Page');
});

app.listen(3000, () => {
    console.log('Server started');
});

Open:

http://localhost:3000/restricted
Output: Access Denied

The route handler does not run because the middleware has already sent the response.

7. Built-in Middleware in Express.js

Express provides built-in middleware for common tasks such as parsing JSON requests and serving static files.

Middleware Purpose
express.json() Parses incoming JSON request bodies.
express.urlencoded() Parses URL-encoded form request bodies.
express.static() Serves static files such as CSS, images, and JavaScript.

8. Using express.json()

When a client sends JSON data in a POST request, express.json() can parse the request body so that it is available through req.body.

Example: Read JSON Data
const express = require('express');
const app = express();

app.use(express.json());

app.post('/student', (req, res) => {
    const student = req.body;

    res.json({
        message: 'Student data received',
        student: student
    });
});

app.listen(3000, () => {
    console.log('Server started');
});

Send a POST request to /student with this JSON body:

{
    "name": "Amit",
    "course": "Node.js"
}

Example response:

{
    "message": "Student data received",
    "student": {
        "name": "Amit",
        "course": "Node.js"
    }
}
Use Postman or another HTTP client to send the POST request. Set the request body type to JSON and the Content-Type header to application/json.

9. Using express.urlencoded()

HTML forms commonly send URL-encoded data. Express can parse this data with express.urlencoded().

Example: Handle Form Data
const express = require('express');
const app = express();

app.use(express.urlencoded({ extended: false }));

app.post('/register', (req, res) => {
    const name = req.body.name;

    res.send('Welcome, ' + name);
});

app.listen(3000, () => {
    console.log('Server started');
});

This middleware parses URL-encoded request bodies. The submitted form data can then be accessed through req.body.

10. Using express.static()

Static middleware serves files directly from a folder. It is commonly used for CSS, images, browser-side JavaScript, and HTML files.

Project Structure

my-app/
│
├── app.js
└── public/
    ├── style.css
    └── logo.png
Example: Serve Static Files
const express = require('express');
const app = express();

app.use(express.static('public'));

app.listen(3000, () => {
    console.log('Server started');
});

With this configuration, you can access:

  • http://localhost:3000/style.css
  • http://localhost:3000/logo.png

The folder name public is not included in the public URL by default.

11. Custom Error-Handling Middleware

Express supports special error-handling middleware that receives an error object as its first argument.

Example: Handle an Error
const express = require('express');
const app = express();

app.get('/error', (req, res, next) => {
    next(new Error('Something went wrong'));
});

// Error-handling middleware
app.use((err, req, res, next) => {
    console.error(err.message);

    res.status(500).send('Internal Server Error');
});

app.listen(3000, () => {
    console.log('Server started');
});

Open http://localhost:3000/error to test it.

Error-handling middleware uses four parameters: (err, req, res, next). In production, avoid exposing sensitive internal error details to clients.

12. Middleware Execution Order

Express processes middleware in the order in which it is registered, provided that the request matches the middleware and control is passed onward.

Example: Execution Order
const express = require('express');
const app = express();

app.use((req, res, next) => {
    console.log('Middleware 1');
    next();
});

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

app.get('/', (req, res) => {
    console.log('Route Handler');
    res.send('Hello Student');
});

app.listen(3000, () => {
    console.log('Server started');
});

When you visit the home page, the log order is:

Middleware 1
Middleware 2
Route Handler

13. Common Middleware Mistakes

Mistake How to Fix It
Forgetting to call next() Call next() when continuing without sending a response.
Sending a response more than once End the response in one appropriate path.
Using req.body before parsing JSON Register express.json() before the relevant routes.
Registering error middleware before routes Usually place error-handling middleware after the routes that may pass errors to it.
Assuming logging is authentication Use proper authentication and authorization checks for protected resources.

14. Practical Assignment

Assignment: Build a Middleware-Based Application

Create an Express application with these requirements:

  1. Create a custom middleware that logs the request method and URL.
  2. Create a home route that displays a welcome message.
  3. Create a /dashboard route with route-level middleware.
  4. Create middleware that returns a 403 response for a restricted route.
  5. Use express.json() to receive JSON data.
  6. Create a POST route that returns the received student information as JSON.
  7. Use express.static() to serve a CSS file from a public folder.
  8. Add custom 404 and error-handling middleware.
Lesson Summary: Middleware functions run during the request-response cycle. You learned application-level and route-level middleware, built-in middleware, request parsing, static file serving, custom error handling, and execution order.

Test Your Knowledge – MCQ Quiz

Select the correct answer for each question.

1. What is the purpose of next() in middleware?
2. Which middleware parses incoming JSON request bodies?
3. How many parameters does standard Express error-handling middleware use?