阿里云主机折上折
  • 微信号
Current Site:Index > Error handling middleware writing techniques

Error handling middleware writing techniques

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

Basic Concepts of Error Handling Middleware

Error handling middleware is the central place for catching and handling errors in Koa2 applications. It is typically registered as the first middleware to ensure it can catch any errors thrown by subsequent middleware. In Koa2, error handling middleware wraps yield next within a try/catch block.

app.use(async (ctx, next) => {
  try {
    await next()
  } catch (err) {
    ctx.status = err.status || 500
    ctx.body = err.message
    ctx.app.emit('error', err, ctx)
  }
})

Error Classification and Handling Strategies

Errors in Koa2 applications can be broadly categorized into three types: operational errors, programming errors, and third-party errors. Operational errors, such as invalid user input, should return 4xx status codes; programming errors, like undefined variables, should return 500 status codes; and third-party errors, such as database connection failures, require specific handling based on the situation.

app.use(async (ctx, next) => {
  try {
    await next()
  } catch (err) {
    // Operational errors
    if (err.isOperational) {
      ctx.status = err.statusCode || 400
      ctx.body = { error: err.message }
    } 
    // Programming errors
    else {
      ctx.status = 500
      ctx.body = 'Internal Server Error'
      // Log the full error details
      console.error(err.stack)
    }
  }
})

Standardization of Error Objects

Creating custom error classes can make error handling more consistent. Extending the Error class allows adding extra properties like statusCode, isOperational, etc.

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

// Usage example
throw new AppError('Invalid user input', 400)

Special Handling of Asynchronous Errors

Koa2 is based on async/await, but certain asynchronous operations, such as errors in setTimeout, are not automatically caught. Special attention is needed for such cases.

app.use(async (ctx, next) => {
  try {
    // Simulate an asynchronous operation
    await new Promise((resolve, reject) => {
      setTimeout(() => {
        try {
          someAsyncOperation()
          resolve()
        } catch (err) {
          reject(err)
        }
      }, 100)
    })
    await next()
  } catch (err) {
    // Error handling
  }
})

Error Logging Strategies

A robust error handling system should include logging. Libraries like winston or pino can be used to log error stacks, request context, and other details.

const logger = require('winston')

app.use(async (ctx, next) => {
  try {
    await next()
  } catch (err) {
    logger.error({
      message: err.message,
      stack: err.stack,
      url: ctx.url,
      method: ctx.method
    })
    // Additional error handling logic
  }
})

Differences Between Development and Production Environments

Development environments typically require detailed error information, while production environments should hide sensitive details.

app.use(async (ctx, next) => {
  try {
    await next()
  } catch (err) {
    ctx.status = err.status || 500
    
    if (process.env.NODE_ENV === 'development') {
      ctx.body = {
        message: err.message,
        stack: err.stack
      }
    } else {
      ctx.body = { message: 'Something went wrong' }
    }
  }
})

Emitting and Listening to Error Events

Koa2 applications can emit error events, making it easier to centralize error handling, such as sending alert emails.

app.on('error', (err, ctx) => {
  // Send an email notification to the admin
  if (err.status >= 500) {
    sendAlertEmail(err, ctx)
  }
})

// In middleware
app.use(async (ctx, next) => {
  try {
    await next()
  } catch (err) {
    ctx.app.emit('error', err, ctx)
    // Additional handling
  }
})

Handling Request Validation Errors

For request validation errors, libraries like joi can be used to validate and return a unified error format.

const Joi = require('joi')

app.use(async (ctx, next) => {
  try {
    const schema = Joi.object({
      username: Joi.string().required(),
      password: Joi.string().min(6).required()
    })
    
    const { error } = schema.validate(ctx.request.body)
    if (error) {
      throw new AppError(error.details[0].message, 400)
    }
    
    await next()
  } catch (err) {
    // Error handling
  }
})

Handling Database Errors

Database operation errors, such as connection failures or query timeouts, require special handling.

app.use(async (ctx, next) => {
  try {
    await next()
  } catch (err) {
    // MongoDB errors
    if (err.name === 'MongoError') {
      if (err.code === 11000) {
        ctx.status = 409
        ctx.body = { error: 'Duplicate key error' }
      } else {
        ctx.status = 503
        ctx.body = { error: 'Database error' }
      }
    }
    // Other error handling
  }
})

Performance Monitoring and Error Correlation

Correlating errors with performance metrics can help analyze root causes. Tools like New Relic can be used for this purpose.

app.use(async (ctx, next) => {
  const start = Date.now()
  try {
    await next()
    const duration = Date.now() - start
    recordResponseTime(ctx.path, duration)
  } catch (err) {
    const duration = Date.now() - start
    recordErrorResponseTime(ctx.path, duration, err)
    throw err
  }
})

Formatting Client Error Responses

A unified error response format helps clients handle errors more effectively. It can include fields like error code, message, and details.

app.use(async (ctx, next) => {
  try {
    await next()
  } catch (err) {
    ctx.status = err.status || 500
    ctx.body = {
      code: err.code || 'UNKNOWN_ERROR',
      message: err.message,
      details: process.env.NODE_ENV === 'development' ? err.stack : undefined,
      timestamp: new Date().toISOString()
    }
  }
})

Impact of Middleware Execution Order

The position of error handling middleware is crucial. It should be registered before other middleware but after foundational middleware like logging and request ID generation.

// Correct registration order
app.use(requestIdMiddleware)
app.use(loggerMiddleware)
app.use(errorHandlerMiddleware)
app.use(bodyParser())
app.use(router.routes())

Testing Error Handling Middleware

Write tests to ensure the error handling middleware works as expected, including simulating various error scenarios.

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

describe('Error Handler', () => {
  it('should handle 404 errors', async () => {
    const res = await request(app).get('/nonexistent')
    expect(res.status).toBe(404)
    expect(res.body.code).toBe('NOT_FOUND')
  })
  
  it('should handle 500 errors', async () => {
    // Simulate a route throwing an error
    app.use('/test-error', ctx => {
      throw new Error('Test error')
    })
    
    const res = await request(app).get('/test-error')
    expect(res.status).toBe(500)
    expect(res.body.code).toBe('INTERNAL_SERVER_ERROR')
  })
})

Error Handling and HTTP Status Codes

Proper use of HTTP status codes helps clients understand the nature of errors. Common status codes and their use cases:

  • 400 Bad Request: Client request error
  • 401 Unauthorized: Authentication required
  • 403 Forbidden: No permission
  • 404 Not Found: Resource not found
  • 429 Too Many Requests: Too many requests
  • 500 Internal Server Error: Server internal error
  • 503 Service Unavailable: Service unavailable
app.use(async (ctx, next) => {
  try {
    await next()
    if (ctx.status === 404) {
      throw new AppError('Not Found', 404)
    }
  } catch (err) {
    // Ensure the status code is set correctly
    ctx.status = err.status || 500
    // Additional handling
  }
})

Configurability of Error Handling Middleware

Make error handling middleware more flexible by using a configuration object to control settings like stack trace visibility and custom formatting functions.

function createErrorHandler(options = {}) {
  const defaults = {
    showStack: process.env.NODE_ENV === 'development',
    formatError: err => ({
      code: err.code,
      message: err.message
    })
  }
  
  const config = { ...defaults, ...options }
  
  return async (ctx, next) => {
    try {
      await next()
    } catch (err) {
      ctx.status = err.status || 500
      ctx.body = config.formatError(err)
      if (config.showStack) {
        ctx.body.stack = err.stack
      }
    }
  }
}

// Usage
app.use(createErrorHandler({
  showStack: true
}))

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

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