Learnwizy Technologies Logo

Learnwizy Technologies

Class 22: Express.js - Middleware

In Express.js, middleware functions are the heart of how requests are processed. They are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle.

Middleware functions can perform tasks like:


Understanding Middleware

Imagine your Express application as an assembly line for HTTP requests. Each station on the assembly line is a middleware function. A request enters the first station, gets processed, and then is passed to the next station, and so on, until a station decides to send a response back to the client.

The key to middleware is the next() function. When a middleware function completes its task, it calls next() to pass control to the next middleware function in the stack. If a middleware function does not call next(), it must terminate the request-response cycle itself (e.g., by sending a response with res.send() or res.json()).

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

// This is a simple custom middleware function
const myLogger = function (req, res, next) {
  console.log('LOGGED:', req.method, req.url, new Date().toISOString());
  next(); // Call next() to pass control to the next middleware/route handler
};

// Apply the middleware to all routes in the application
app.use(myLogger);

// Define a route
app.get('/', (req, res) => {
  res.send('Hello World!');
});

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

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

When you run this server and visit / or /users, you'll see the log message from myLogger in your terminal before the "Hello World!" or "Users list page" response is sent.


Types of Middleware

Express.js supports several types of middleware:

1. Application-level middleware:

Bound to an instance of the app object by using app.use() or app.METHOD() (e.g., app.get(), app.post()).

// Applied to all requests
app.use(myLogger);

// Applied only to GET requests to /api/products
app.get('/api/products', (req, res, next) => {
  // This middleware runs only for this specific route
  console.log('Accessing products API...');
  next();
}, (req, res) => {
  res.json({ products: [] });
});

2. Router-level middleware:

Works in the same way as application-level middleware, but it is bound to an instance of express.Router(). This allows you to modularize your routes and middleware.

// routes/userRoutes.js
const express = require('express');
const router = express.Router();

// Middleware specific to this router
router.use((req, res, next) => {
  console.log('Time:', Date.now());
  next();
});

// Define routes for this router
router.get('/', (req, res) => {
  res.send('Users homepage');
});

router.get('/:id', (req, res) => {
  res.send(`User: ${req.params.id}`);
});

module.exports = router;

// In your main server.js:
// const userRoutes = require('./routes/userRoutes');
// app.use('/users', userRoutes); // Mount the router at /users

3. Error-handling middleware:

These functions have four arguments: (err, req, res, next). They are specifically designed to catch and handle errors that occur during the request-response cycle.

// Must be the last middleware loaded
app.use((err, req, res, next) => {
  console.error(err.stack); // Log the error stack for debugging
  res.status(500).send('Something broke!'); // Send a generic error response
});

4. Built-in middleware:

Express comes with built-in middleware functions like express.static, express.json, and express.urlencoded.

5. Third-party middleware:

Middleware functions loaded using npm, such as morgan for logging, cors for handling Cross-Origin Resource Sharing, or helmet for setting security headers.


Built-in Express Middleware

Express provides several built-in middleware functions to handle common tasks.


Creating Custom Middleware

You can write your own custom middleware functions to add specific logic to your application.

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

// Custom middleware for authentication check
const authenticateUser = (req, res, next) => {
  const isAuthenticated = true; // In a real app, check token, session, etc.
  if (isAuthenticated) {
    req.user = { id: 1, name: 'John Doe', role: 'admin' }; // Attach user info to request
    next(); // User is authenticated, proceed to the next middleware/route
  } else {
    res.status(401).send('Unauthorized: Please log in.'); // Not authenticated
  }
};

// Custom middleware for logging request details
const requestLogger = (req, res, next) => {
  const start = Date.now();
  res.on('finish', () => { // Event listener for when the response is finished
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.originalUrl} - ${res.statusCode} - ${duration}ms`);
  });
  next();
};

app.use(requestLogger); // Apply logging middleware globally

// Apply authentication middleware only to routes under /admin
app.get('/admin', authenticateUser, (req, res) => {
  res.send(`Welcome, admin ${req.user.name}!`); // Access user info from req.user
});

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

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

Common Use Cases for Middleware

Middleware is a fundamental and powerful concept in Express.js. By chaining middleware functions, you can build complex and modular request processing pipelines, keeping your code clean and organized. In the next class, we'll explore RESTful API principles, which will guide us in structuring our Express.js APIs.