阿里云主机折上折
  • 微信号
Current Site:Index > Middleware mechanism and execution flow

Middleware mechanism and execution flow

Author:Chuan Chen 阅读数:43353人阅读 分类: Node.js

Middleware Mechanism and Execution Flow

Express's middleware mechanism is one of its core features, allowing developers to insert custom processing logic into the request and response cycle. This mechanism is organized in the form of a function stack, where each middleware function can access the request object, response object, and a reference to the next middleware function.

Basic Structure of Middleware

A typical Express middleware function consists of three parameters: req, res, and next. When the middleware completes its task, it can call next() to pass control to the next middleware or directly end the request-response cycle.

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

// Simplest logging middleware
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

// Route handling middleware
app.get('/', (req, res) => {
  res.send('Hello World');
});

app.listen(3000);

Types of Middleware

Express supports various types of middleware, each differing in invocation timing and scope:

  1. Application-level middleware: Bound to the application instance via app.use() or app.METHOD()
  2. Router-level middleware: Bound to a router instance via router.use()
  3. Error-handling middleware: Accepts four parameters (err, req, res, next)
  4. Built-in middleware: Such as express.static
  5. Third-party middleware: Such as body-parser
// Application-level middleware example
app.use(express.json()); // Parse JSON request body
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded request body

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

Middleware Execution Order

The execution order of middleware is crucial. Express executes them sequentially in the order they are registered, determining how requests are processed and responses are constructed.

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

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

app.get('/user', (req, res) => {
  res.send('User profile');
});

In the example above, when accessing the /user path, the console will output "First middleware" and "Second middleware" in sequence.

Middleware Flow Control

The next() function is key to controlling middleware flow. It can:

  1. Pass control to the next middleware
  2. Skip remaining middleware
  3. Pass errors to error-handling middleware
// Token validation middleware
app.use('/api', (req, res, next) => {
  const token = req.headers['authorization'];
  if (!token) {
    return res.status(401).send('Unauthorized');
  }
  next();
});

// This middleware executes only if token validation passes
app.get('/api/data', (req, res) => {
  res.json({ data: 'secret' });
});

Asynchronous Middleware Handling

Modern Express supports asynchronous middleware functions using async/await syntax:

app.use(async (req, res, next) => {
  try {
    const user = await User.findById(req.userId);
    req.user = user;
    next();
  } catch (err) {
    next(err);
  }
});

Middleware Composition and Reuse

Express provides Router instances to organize and reuse middleware:

const router = express.Router();

// Add middleware to all routes
router.use((req, res, next) => {
  console.log('Time:', Date.now());
  next();
});

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

// Mount the router to the app
app.use('/api', router);

Error Handling in Middleware

Error-handling middleware requires four parameters and is typically placed after other middleware:

app.get('/error', (req, res, next) => {
  const err = new Error('Something went wrong');
  next(err); // Pass to error-handling middleware
});

// Error-handling middleware
app.use((err, req, res, next) => {
  res.status(500).json({
    error: err.message,
    stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
  });
});

Performance Considerations for Middleware

Middleware is powerful but can impact performance if misused:

  1. Avoid unnecessary computations in middleware
  2. Terminate invalid requests early
  3. Use caching judiciously
// Performance optimization example: Caching static resources
const cacheMiddleware = (req, res, next) => {
  res.set('Cache-Control', 'public, max-age=3600');
  next();
};

app.use('/static', cacheMiddleware, express.static('public'));

Practical Use Cases for Middleware

  1. Request logging: Record detailed information for each request
  2. Authentication: Validate user credentials
  3. Data validation: Check request data validity
  4. Response formatting: Standardize API response formats
  5. Rate limiting: Prevent API abuse
// Request logging middleware
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`);
  });
  next();
});

// Rate-limiting middleware
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // Limit 100 requests per IP
});
app.use('/api/', limiter);

Testing and Debugging Middleware

Testing middleware requires simulating request and response objects:

// Testing authentication middleware
test('auth middleware rejects requests without token', () => {
  const req = {
    headers: {}
  };
  const res = {
    status: jest.fn().mockReturnThis(),
    send: jest.fn()
  };
  const next = jest.fn();
  
  authMiddleware(req, res, next);
  
  expect(res.status).toHaveBeenCalledWith(401);
  expect(res.send).toHaveBeenCalledWith('Unauthorized');
  expect(next).not.toHaveBeenCalled();
});

Advanced Middleware Patterns

  1. Middleware factories: Create configurable middleware
  2. Middleware composition: Combine multiple middleware into one
  3. Conditional middleware: Apply middleware based on conditions
// Middleware factory example: Create a configurable logging middleware
function createLoggerMiddleware(format) {
  return (req, res, next) => {
    const message = format
      .replace(':method', req.method)
      .replace(':url', req.url);
    console.log(message);
    next();
  };
}

app.use(createLoggerMiddleware(':method :url'));

// Conditional middleware example
function conditionalMiddleware(condition, middleware) {
  return (req, res, next) => {
    if (condition(req)) {
      middleware(req, res, next);
    } else {
      next();
    }
  };
}

app.use(conditionalMiddleware(
  req => req.path.startsWith('/api'),
  bodyParser.json()
));

Middleware and Express 5 New Features

Express 5 introduces several improvements to middleware handling:

  1. Better asynchronous error handling
  2. Improved route parameter processing
  3. More flexible response methods
// Improved error handling in Express 5
app.get('/user/:id', async (req, res, next) => {
  try {
    const user = await User.find(req.params.id);
    if (!user) throw new Error('User not found');
    res.json(user);
  } catch (err) {
    next(err); // Automatically passed to error-handling middleware
  }
});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.