Skip to main content

Command Palette

Search for a command to run...

What is Middleware in Express and How It Works

Published
6 min read
What is Middleware in Express and How It Works

Middleware sits between incoming requests and your route handlers. It intercepts requests, does something with them, and either passes them along or stops them. Understanding middleware is key to building Express applications.

What Is Middleware?

Middleware is a function that has access to the request and response objects. It can modify them, perform checks, or stop the request.

Think of middleware as checkpoints in an airport:

  • Security check (middleware validates the request)

  • Baggage scan (middleware processes data)

  • Gate (final destination/route handler)

Each checkpoint can allow you through to the next, or stop you.

In Express, middleware functions have this signature:

function middleware(req, res, next) {
  // Do something
  next(); // Pass to next middleware
}

The next() function passes control to the next middleware or route handler.

How Middleware Works

When a request arrives, Express processes it through middleware in order:

Request comes in
     |
     v
Middleware 1 (if next() called, continue)
     |
     v
Middleware 2 (if next() called, continue)
     |
     v
Route Handler (your actual route)
     |
     v
Response sent

Each middleware either calls next() to continue or sends a response to stop.

Using Express-Provided Middleware

Express provides built-in middleware for common tasks:

import express from 'express';

const app = express();

// Parse JSON requests
app.use(express.json());

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

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

app.post('/data', (req, res) => {
  // Thanks to express.json() middleware, req.body is parsed
  console.log(req.body);
  res.send('Data received');
});

app.listen(3000);

express.json() middleware automatically parses incoming JSON and populates req.body.

Creating Custom Middleware

Write your own middleware for custom logic:

import express from 'express';

const app = express();

// Custom logging middleware
function logger(req, res, next) {
  console.log(`\({req.method} \){req.url}`);
  next(); // Pass to next middleware
}

app.use(logger);

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

app.listen(3000);

Every request is logged, then passed to the route handler.

Output for GET /:

GET /

Middleware Execution Order

Middleware runs in the order you define it:

import express from 'express';

const app = express();

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

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

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

app.listen(3000);

For a GET / request, output is:

1: First middleware
2: Second middleware
3: Route handler

Order matters. Middleware defined first runs first.

The next() Function

Calling next() passes control to the next function in the chain. Not calling it stops execution:

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

app.use((req, res, next) => {
  console.log('Middleware 2');
  // Not calling next() - stops here
});

app.get('/', (req, res) => {
  console.log('Route'); // Never reached
  res.send('Hi');
});

This stops at "Middleware 2". The route handler is never called.

Types of Middleware

Application-Level Middleware

Runs for all routes or specific routes:

import express from 'express';

const app = express();

// Runs for ALL routes
app.use((req, res, next) => {
  console.log('Global middleware');
  next();
});

// Runs only for /admin routes
app.use('/admin', (req, res, next) => {
  console.log('Admin middleware');
  next();
});

app.get('/', (req, res) => res.send('Home'));
app.get('/admin/users', (req, res) => res.send('Admin page'));

app.listen(3000);

Router-Level Middleware

Middleware on a specific router:

import express from 'express';

const app = express();
const router = express.Router();

router.use((req, res, next) => {
  console.log('Router middleware');
  next();
});

router.get('/users', (req, res) => res.send('Users'));

app.use('/api', router);
app.listen(3000);

Built-In Middleware

Express provides standard middleware:

app.use(express.json());
app.use(express.static('public'));
app.use(express.urlencoded({ extended: true }));

Third-Party Middleware

Use packages from npm:

npm install cors
import cors from 'cors';

app.use(cors()); // Enable CORS

Real-World Middleware Examples

Authentication Middleware

function authenticateToken(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).send('No token');
  }

  // Verify token (simplified)
  if (token === 'valid-token') {
    req.user = { id: 1 };
    next();
  } else {
    res.status(403).send('Invalid token');
  }
}

app.get('/protected', authenticateToken, (req, res) => {
  res.send(`Hello user ${req.user.id}`);
});

Error Handling Middleware

app.get('/data', (req, res, next) => {
  try {
    throw new Error('Something went wrong');
  } catch (error) {
    next(error); // Pass to error handler
  }
});

// Error handling middleware (must have 4 parameters)
app.use((err, req, res, next) => {
  console.error(err.message);
  res.status(500).send('Server error');
});

Request Timing Middleware

app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`\({req.method} \){req.url} - ${duration}ms`);
  });

  next();
});

Middleware with Parameters

Middleware factories return middleware functions:

function logMessages(format) {
  return (req, res, next) => {
    if (format === 'simple') {
      console.log(`\({req.method} \){req.url}`);
    } else if (format === 'detailed') {
      console.log(`\({req.method} \){req.url} - ${req.headers['user-agent']}`);
    }
    next();
  };
}

app.use(logMessages('simple'));
// or
app.use(logMessages('detailed'));

Complete Example with Multiple Middleware

import express from 'express';

const app = express();

// Built-in middleware
app.use(express.json());

// Custom logging middleware
app.use((req, res, next) => {
  console.log(`[\({new Date().toISOString()}] \){req.method} ${req.url}`);
  next();
});

// Authentication middleware
function authenticate(req, res, next) {
  const token = req.query.token;
  if (token === 'secret') {
    req.user = { id: 1, name: 'John' };
    next();
  } else {
    res.status(401).send('Unauthorized');
  }
}

// Request validation middleware
function validateRequest(req, res, next) {
  if (!req.body.email) {
    return res.status(400).send('Email required');
  }
  next();
}

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

// Protected route
app.get('/profile', authenticate, (req, res) => {
  res.send(`Hello ${req.user.name}`);
});

// Route with validation
app.post('/register', validateRequest, (req, res) => {
  res.send(`Registered ${req.body.email}`);
});

// Error handling middleware (must be last)
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: 'Server error' });
});

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

Common Mistakes

Mistake 1: Forgetting to call next()

// Wrong - middleware stops here
app.use((req, res, next) => {
  console.log('Middleware');
  // No next() call - route never executes
});

app.get('/', (req, res) => {
  res.send('Hello'); // Never reached
});

// Right
app.use((req, res, next) => {
  console.log('Middleware');
  next(); // Passes to next handler
});

Mistake 2: Wrong middleware order

// Wrong - middleware after route can't help
app.get('/', (req, res) => {
  res.send(req.body); // undefined - JSON not parsed yet
});

app.use(express.json()); // Too late!

// Right - middleware before routes
app.use(express.json());
app.get('/', (req, res) => {
  res.send(req.body); // Properly parsed
});

Mistake 3: Error handler must have 4 parameters

// Wrong - Express won't recognize as error handler
app.use((err, req, res) => {
  res.send('Error');
});

// Right - all 4 parameters required
app.use((err, req, res, next) => {
  res.send('Error');
});

Key Takeaways

  • Middleware intercepts requests and can modify them or stop them

  • Middleware runs in the order it's defined

  • Call next() to pass to the next middleware

  • Express provides built-in middleware like express.json()

  • You can create custom middleware for any task

  • Application-level middleware runs for all/specific routes

  • Error handlers are middleware with 4 parameters

  • Order matters: define middleware before routes that need it

Middleware is the backbone of Express applications. Master it, and you can build clean, modular, and powerful web applications.