阿里云主机折上折
  • 微信号
Current Site:Index > Special usage of error handling middleware

Special usage of error handling middleware

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

Basic Concepts of Error Handling Middleware

In Express, error handling middleware is almost identical to regular middleware, but with one key difference: it accepts four parameters instead of three. The fourth parameter, err, represents the error object. The basic structure is as follows:

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something went wrong!');
});

This middleware captures all errors thrown in the application. It's important to note that error handling middleware should be placed after all other middleware and routes to ensure it catches all potential errors.

Special Parameter Order in Error Middleware

The parameter order in error handling middleware is critical. Express distinguishes between regular middleware and error handling middleware based on the number of parameters:

// Regular middleware - three parameters
app.use((req, res, next) => {
  // Middleware logic
});

// Error handling middleware - four parameters
app.use((err, req, res, next) => {
  // Error handling logic
});

If the parameter order is incorrect, Express won't recognize it as error handling middleware. For example, the following code won't work as error handling middleware:

// Incorrect parameter order - won't be recognized as error handling middleware
app.use((req, err, res, next) => {
  // This code will never execute
});

Manually Triggering Error Handling

You can manually pass control to error handling middleware using the next() function:

app.get('/problematic-route', (req, res, next) => {
  try {
    // Code that might throw an error
    throw new Error('Intentional error');
  } catch (err) {
    next(err); // Pass the error to the error handling middleware
  }
});

This approach is particularly useful for handling errors in asynchronous code, as try-catch cannot catch errors in asynchronous operations:

app.get('/async-route', async (req, res, next) => {
  try {
    await someAsyncOperation();
  } catch (err) {
    next(err); // Must manually pass the error
  }
});

Chaining Error Handling Middleware

Error handling middleware can be chained like regular middleware, allowing for multiple layers of error handling:

// First error handling middleware - logs the error
app.use((err, req, res, next) => {
  console.error('Error logged:', err);
  next(err); // Pass to the next error handling middleware
});

// Second error handling middleware - sends the response
app.use((err, req, res, next) => {
  res.status(500).json({
    error: err.message
  });
});

This layered approach enables complex error handling logic, such as logging errors first and then returning different responses based on error types.

Route-Specific Error Handling

You can create dedicated error handling middleware for specific routes:

const router = express.Router();

// Route-specific error handling
router.use((err, req, res, next) => {
  if (err.type === 'RouteSpecificError') {
    res.status(400).send('Route-specific error handling');
  } else {
    next(err); // Pass to application-level error handling
  }
});

router.get('/special', (req, res, next) => {
  const err = new Error('RouteSpecificError');
  err.type = 'RouteSpecificError';
  next(err);
});

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

This approach allows for customized error handling logic for different routes or route groups.

Categorized Error Handling

You can return different HTTP status codes and responses based on error types:

app.use((err, req, res, next) => {
  if (err instanceof ValidationError) {
    return res.status(400).json({
      error: 'Validation error',
      details: err.details
    });
  }
  
  if (err instanceof DatabaseError) {
    return res.status(503).json({
      error: 'Database error',
      suggestion: 'Please try again later'
    });
  }
  
  // Unknown error
  res.status(500).json({
    error: 'Internal server error'
  });
});

This pattern enables APIs to provide more meaningful responses based on different error types.

Asynchronous Error Handling Techniques

Error handling in asynchronous functions requires special attention. Express 5 will natively support error bubbling in async functions, but in Express 4, manual handling is required:

// Approach in Express 4
app.get('/async', (req, res, next) => {
  someAsyncFunction()
    .then(result => res.json(result))
    .catch(next); // Pass Promise rejection to error handling middleware
});

// Or using async/await
app.get('/async-await', async (req, res, next) => {
  try {
    const result = await someAsyncFunction();
    res.json(result);
  } catch (err) {
    next(err);
  }
});

Combining Error Middleware with Promises

You can create a wrapper function to automatically catch Promise rejections:

function asyncHandler(fn) {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next))
      .catch(next);
  };
}

app.get('/wrapped', asyncHandler(async (req, res) => {
  const data = await fetchData();
  if (!data.valid) {
    throw new Error('Invalid data'); // Automatically caught by asyncHandler
  }
  res.json(data);
}));

This pattern reduces boilerplate code, making async routes more concise.

Debugging Techniques for Error Middleware

In development environments, more detailed error information may be needed:

app.use((err, req, res, next) => {
  const isDevelopment = process.env.NODE_ENV === 'development';
  
  res.status(err.status || 500).json({
    message: err.message,
    stack: isDevelopment ? err.stack : undefined,
    ...(isDevelopment && { originalError: err })
  });
});

This ensures stack traces and other sensitive information are only exposed in development.

Performance Considerations for Error Middleware

Avoid time-consuming operations in error handling middleware, as they may impact the application's error recovery:

// Not recommended - synchronous logging may block the event loop
app.use((err, req, res, next) => {
  fs.writeFileSync('error.log', err.stack);
  next(err);
});

// Recommended - use asynchronous logging
app.use(async (err, req, res, next) => {
  await logErrorAsync(err);
  next(err);
});

Enhancing Handling with Custom Error Classes

Creating custom error classes can make error handling more structured:

class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
    Error.captureStackTrace(this, this.constructor);
  }
}

// Using custom errors
app.get('/custom-error', (req, res, next) => {
  next(new AppError('Resource not found', 404));
});

// Handling custom errors
app.use((err, req, res, next) => {
  if (err instanceof AppError) {
    return res.status(err.statusCode).json({
      status: 'error',
      message: err.message
    });
  }
  next(err);
});

Integrating Error Middleware with Third-Party Logging Services

You can send errors to external logging services like Sentry or Loggly:

const Sentry = require('@sentry/node');

app.use((err, req, res, next) => {
  Sentry.captureException(err);
  
  // Still need to return a response to the client
  res.status(500).json({
    error: 'Internal server error',
    referenceId: Sentry.lastEventId()
  });
});

Special Handling for 404 Errors

Although 404 is not a technical error, it can be uniformly handled via error handling middleware:

// Catch 404 and forward to error handler
app.use((req, res, next) => {
  next(new AppError('Not found', 404));
});

// Then handle uniformly in error handling middleware
app.use((err, req, res, next) => {
  if (err.statusCode === 404) {
    return res.status(404).render('404');
  }
  next(err);
});

Testing Strategies for Error Middleware

Testing error handling middleware requires simulating error scenarios:

const request = require('supertest');
const app = require('../app');

describe('Error Handling Middleware', () => {
  it('should handle 500 errors', async () => {
    // Simulate a test route that throws an error
    app.get('/test-error', (req, res, next) => {
      next(new Error('Test error'));
    });
    
    const res = await request(app).get('/test-error');
    expect(res.status).toBe(500);
    expect(res.body.error).toBeDefined();
  });
});

Combining Error Middleware with API Documentation

You can use error handling middleware to automatically generate API error documentation:

const apiDocs = {
  '/api/users': {
    errors: [
      { code: 400, description: 'Invalid input' },
      { code: 500, description: 'Server error' }
    ]
  }
};

app.use((err, req, res, next) => {
  const routeDocs = apiDocs[req.path];
  if (routeDocs) {
    err.documentation = routeDocs.errors.find(e => e.code === err.statusCode);
  }
  next(err);
});

Security Considerations for Error Middleware

Avoid leaking sensitive information in error responses:

app.use((err, req, res, next) => {
  // Sanitize the error object, removing sensitive information
  const safeError = {
    message: err.message,
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  };
  
  res.status(err.status || 500).json({
    error: safeError
  });
});

Multilingual Support in Error Middleware

Return localized error messages based on request headers:

const errorMessages = {
  en: {
    database_error: 'Database error'
  },
  zh: {
    database_error: '数据库错误'
  }
};

app.use((err, req, res, next) => {
  const lang = req.acceptsLanguages('en', 'zh') || 'en';
  const message = errorMessages[lang][err.code] || err.message;
  
  res.status(err.status || 500).json({
    error: message
  });
});

Collaborating with Frontend Frameworks in Error Middleware

Return structured errors for frontend frameworks to handle:

app.use((err, req, res, next) => {
  res.status(err.status || 500).json({
    error: {
      message: err.message,
      code: err.code,
      fields: err.fields // For form validation errors
    }
  });
});

This allows the frontend to uniformly handle the API's error structure.

Integrating Error Middleware with Monitoring Systems

Integrate errors with monitoring systems:

const statsd = require('node-statsd')();

app.use((err, req, res, next) => {
  // Increment counters based on error type
  statsd.increment(`errors.${err.constructor.name}`);
  
  next(err);
});

Request Retry Logic in Error Middleware

For temporary errors, return retry suggestions:

app.use((err, req, res, next) => {
  if (err.isRetryable) {
    res.set('Retry-After', '10');
    return res.status(503).json({
      error: 'Service temporarily unavailable',
      retryAfter: 10
    });
  }
  next(err);
});

Circuit Breaker Pattern in Error Middleware

Implement a simple circuit breaker pattern:

const circuitBreaker = {
  isOpen: false,
  lastFailure: null
};

app.use((err, req, res, next) => {
  if (err.isCircuitBreaker) {
    circuitBreaker.isOpen = true;
    circuitBreaker.lastFailure = Date.now();
  }
  
  if (circuitBreaker.isOpen) {
    return res.status(503).json({
      error: 'Service temporarily unavailable',
      willRetryAt: new Date(circuitBreaker.lastFailure + 60000)
    });
  }
  
  next(err);
});

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

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.