error-handling

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Error Handling

错误处理

Handle errors gracefully and consistently across your application.
在你的应用中优雅且一致地处理错误。

When to Use This Skill

何时使用该方案

  • API error responses
  • Database errors
  • External service failures
  • Validation errors
  • Authentication/authorization errors
  • API错误响应
  • 数据库错误
  • 外部服务故障
  • 验证错误
  • 身份验证/授权错误

Error Response Format

错误响应格式

json
{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "User not found",
    "details": { "userId": "123" },
    "requestId": "req_abc123"
  }
}
json
{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "User not found",
    "details": { "userId": "123" },
    "requestId": "req_abc123"
  }
}

TypeScript Implementation

TypeScript实现

Custom Error Classes

自定义错误类

typescript
// errors/app-error.ts
export class AppError extends Error {
  constructor(
    public code: string,
    public message: string,
    public statusCode: number = 500,
    public details?: Record<string, unknown>,
    public isOperational: boolean = true
  ) {
    super(message);
    this.name = 'AppError';
    Error.captureStackTrace(this, this.constructor);
  }
}

// Common error types
export class NotFoundError extends AppError {
  constructor(resource: string, id?: string) {
    super(
      'RESOURCE_NOT_FOUND',
      `${resource} not found`,
      404,
      id ? { [`${resource.toLowerCase()}Id`]: id } : undefined
    );
  }
}

export class ValidationError extends AppError {
  constructor(details: Array<{ field: string; message: string }>) {
    super('VALIDATION_ERROR', 'Validation failed', 400, { errors: details });
  }
}

export class UnauthorizedError extends AppError {
  constructor(message = 'Authentication required') {
    super('UNAUTHORIZED', message, 401);
  }
}

export class ForbiddenError extends AppError {
  constructor(message = 'Access denied') {
    super('FORBIDDEN', message, 403);
  }
}

export class ConflictError extends AppError {
  constructor(message: string, details?: Record<string, unknown>) {
    super('CONFLICT', message, 409, details);
  }
}

export class RateLimitError extends AppError {
  constructor(retryAfter: number) {
    super('RATE_LIMITED', 'Too many requests', 429, { retryAfter });
  }
}

export class ExternalServiceError extends AppError {
  constructor(service: string, originalError?: Error) {
    super(
      'EXTERNAL_SERVICE_ERROR',
      `${service} service unavailable`,
      503,
      { service, originalMessage: originalError?.message }
    );
  }
}
typescript
// errors/app-error.ts
export class AppError extends Error {
  constructor(
    public code: string,
    public message: string,
    public statusCode: number = 500,
    public details?: Record<string, unknown>,
    public isOperational: boolean = true
  ) {
    super(message);
    this.name = 'AppError';
    Error.captureStackTrace(this, this.constructor);
  }
}

// Common error types
export class NotFoundError extends AppError {
  constructor(resource: string, id?: string) {
    super(
      'RESOURCE_NOT_FOUND',
      `${resource} not found`,
      404,
      id ? { [`${resource.toLowerCase()}Id`]: id } : undefined
    );
  }
}

export class ValidationError extends AppError {
  constructor(details: Array<{ field: string; message: string }>) {
    super('VALIDATION_ERROR', 'Validation failed', 400, { errors: details });
  }
}

export class UnauthorizedError extends AppError {
  constructor(message = 'Authentication required') {
    super('UNAUTHORIZED', message, 401);
  }
}

export class ForbiddenError extends AppError {
  constructor(message = 'Access denied') {
    super('FORBIDDEN', message, 403);
  }
}

export class ConflictError extends AppError {
  constructor(message: string, details?: Record<string, unknown>) {
    super('CONFLICT', message, 409, details);
  }
}

export class RateLimitError extends AppError {
  constructor(retryAfter: number) {
    super('RATE_LIMITED', 'Too many requests', 429, { retryAfter });
  }
}

export class ExternalServiceError extends AppError {
  constructor(service: string, originalError?: Error) {
    super(
      'EXTERNAL_SERVICE_ERROR',
      `${service} service unavailable`,
      503,
      { service, originalMessage: originalError?.message }
    );
  }
}

Error Handler Middleware

错误处理中间件

typescript
// middleware/error-handler.ts
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../errors/app-error';
import { logger } from '../utils/logger';

interface ErrorResponse {
  error: {
    code: string;
    message: string;
    details?: Record<string, unknown>;
    requestId?: string;
  };
}

export function errorHandler(
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) {
  const requestId = req.headers['x-request-id'] as string;

  // Handle known operational errors
  if (err instanceof AppError) {
    logger.warn('Operational error', {
      code: err.code,
      message: err.message,
      statusCode: err.statusCode,
      requestId,
      path: req.path,
    });

    const response: ErrorResponse = {
      error: {
        code: err.code,
        message: err.message,
        details: err.details,
        requestId,
      },
    };

    return res.status(err.statusCode).json(response);
  }

  // Handle Prisma errors
  if (err.name === 'PrismaClientKnownRequestError') {
    const prismaError = err as any;
    if (prismaError.code === 'P2002') {
      return res.status(409).json({
        error: {
          code: 'DUPLICATE_ENTRY',
          message: 'Resource already exists',
          details: { fields: prismaError.meta?.target },
          requestId,
        },
      });
    }
    if (prismaError.code === 'P2025') {
      return res.status(404).json({
        error: {
          code: 'RESOURCE_NOT_FOUND',
          message: 'Resource not found',
          requestId,
        },
      });
    }
  }

  // Handle unknown errors (programming errors)
  logger.error('Unhandled error', {
    error: err.message,
    stack: err.stack,
    requestId,
    path: req.path,
  });

  // Don't leak error details in production
  const message = process.env.NODE_ENV === 'production'
    ? 'Internal server error'
    : err.message;

  return res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message,
      requestId,
    },
  });
}
typescript
// middleware/error-handler.ts
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../errors/app-error';
import { logger } from '../utils/logger';

interface ErrorResponse {
  error: {
    code: string;
    message: string;
    details?: Record<string, unknown>;
    requestId?: string;
  };
}

export function errorHandler(
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) {
  const requestId = req.headers['x-request-id'] as string;

  // Handle known operational errors
  if (err instanceof AppError) {
    logger.warn('Operational error', {
      code: err.code,
      message: err.message,
      statusCode: err.statusCode,
      requestId,
      path: req.path,
    });

    const response: ErrorResponse = {
      error: {
        code: err.code,
        message: err.message,
        details: err.details,
        requestId,
      },
    };

    return res.status(err.statusCode).json(response);
  }

  // Handle Prisma errors
  if (err.name === 'PrismaClientKnownRequestError') {
    const prismaError = err as any;
    if (prismaError.code === 'P2002') {
      return res.status(409).json({
        error: {
          code: 'DUPLICATE_ENTRY',
          message: 'Resource already exists',
          details: { fields: prismaError.meta?.target },
          requestId,
        },
      });
    }
    if (prismaError.code === 'P2025') {
      return res.status(404).json({
        error: {
          code: 'RESOURCE_NOT_FOUND',
          message: 'Resource not found',
          requestId,
        },
      });
    }
  }

  // Handle unknown errors (programming errors)
  logger.error('Unhandled error', {
    error: err.message,
    stack: err.stack,
    requestId,
    path: req.path,
  });

  // Don't leak error details in production
  const message = process.env.NODE_ENV === 'production'
    ? 'Internal server error'
    : err.message;

  return res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message,
      requestId,
    },
  });
}

Async Handler Wrapper

异步处理包装器

typescript
// utils/async-handler.ts
import { Request, Response, NextFunction, RequestHandler } from 'express';

type AsyncRequestHandler = (
  req: Request,
  res: Response,
  next: NextFunction
) => Promise<any>;

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

// Usage
router.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await userService.findById(req.params.id);
  if (!user) {
    throw new NotFoundError('User', req.params.id);
  }
  res.json(user);
}));
typescript
// utils/async-handler.ts
import { Request, Response, NextFunction, RequestHandler } from 'express';

type AsyncRequestHandler = (
  req: Request,
  res: Response,
  next: NextFunction
) => Promise<any>;

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

// Usage
router.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await userService.findById(req.params.id);
  if (!user) {
    throw new NotFoundError('User', req.params.id);
  }
  res.json(user);
}));

Service Layer Error Handling

服务层错误处理

typescript
// services/user-service.ts
import { NotFoundError, ConflictError } from '../errors/app-error';

class UserService {
  async findById(id: string): Promise<User> {
    const user = await db.users.findUnique({ where: { id } });
    if (!user) {
      throw new NotFoundError('User', id);
    }
    return user;
  }

  async create(data: CreateUserInput): Promise<User> {
    const existing = await db.users.findUnique({ where: { email: data.email } });
    if (existing) {
      throw new ConflictError('Email already registered', { email: data.email });
    }
    return db.users.create({ data });
  }

  async updateEmail(userId: string, newEmail: string): Promise<User> {
    try {
      return await db.users.update({
        where: { id: userId },
        data: { email: newEmail },
      });
    } catch (error) {
      if (error.code === 'P2002') {
        throw new ConflictError('Email already in use');
      }
      throw error;
    }
  }
}
typescript
// services/user-service.ts
import { NotFoundError, ConflictError } from '../errors/app-error';

class UserService {
  async findById(id: string): Promise<User> {
    const user = await db.users.findUnique({ where: { id } });
    if (!user) {
      throw new NotFoundError('User', id);
    }
    return user;
  }

  async create(data: CreateUserInput): Promise<User> {
    const existing = await db.users.findUnique({ where: { email: data.email } });
    if (existing) {
      throw new ConflictError('Email already registered', { email: data.email });
    }
    return db.users.create({ data });
  }

  async updateEmail(userId: string, newEmail: string): Promise<User> {
    try {
      return await db.users.update({
        where: { id: userId },
        data: { email: newEmail },
      });
    } catch (error) {
      if (error.code === 'P2002') {
        throw new ConflictError('Email already in use');
      }
      throw error;
    }
  }
}

Python Implementation

Python实现

python
undefined
python
undefined

errors/app_error.py

errors/app_error.py

from dataclasses import dataclass from typing import Optional, Any
@dataclass class AppError(Exception): code: str message: str status_code: int = 500 details: Optional[dict[str, Any]] = None
class NotFoundError(AppError): def init(self, resource: str, id: str = None): super().init( code="RESOURCE_NOT_FOUND", message=f"{resource} not found", status_code=404, details={f"{resource.lower()}_id": id} if id else None, )
class ValidationError(AppError): def init(self, errors: list[dict]): super().init( code="VALIDATION_ERROR", message="Validation failed", status_code=400, details={"errors": errors}, )
class UnauthorizedError(AppError): def init(self, message: str = "Authentication required"): super().init(code="UNAUTHORIZED", message=message, status_code=401)
class ForbiddenError(AppError): def init(self, message: str = "Access denied"): super().init(code="FORBIDDEN", message=message, status_code=403)
undefined
from dataclasses import dataclass from typing import Optional, Any
@dataclass class AppError(Exception): code: str message: str status_code: int = 500 details: Optional[dict[str, Any]] = None
class NotFoundError(AppError): def init(self, resource: str, id: str = None): super().init( code="RESOURCE_NOT_FOUND", message=f"{resource} not found", status_code=404, details={f"{resource.lower()}_id": id} if id else None, )
class ValidationError(AppError): def init(self, errors: list[dict]): super().init( code="VALIDATION_ERROR", message="Validation failed", status_code=400, details={"errors": errors}, )
class UnauthorizedError(AppError): def init(self, message: str = "Authentication required"): super().init(code="UNAUTHORIZED", message=message, status_code=401)
class ForbiddenError(AppError): def init(self, message: str = "Access denied"): super().init(code="FORBIDDEN", message=message, status_code=403)
undefined

FastAPI Error Handler

FastAPI错误处理

python
undefined
python
undefined

middleware/error_handler.py

middleware/error_handler.py

from fastapi import Request, HTTPException from fastapi.responses import JSONResponse from errors.app_error import AppError
async def app_error_handler(request: Request, exc: AppError): return JSONResponse( status_code=exc.status_code, content={ "error": { "code": exc.code, "message": exc.message, "details": exc.details, "requestId": request.headers.get("x-request-id"), } }, )
from fastapi import Request, HTTPException from fastapi.responses import JSONResponse from errors.app_error import AppError
async def app_error_handler(request: Request, exc: AppError): return JSONResponse( status_code=exc.status_code, content={ "error": { "code": exc.code, "message": exc.message, "details": exc.details, "requestId": request.headers.get("x-request-id"), } }, )

Register in app

Register in app

app.add_exception_handler(AppError, app_error_handler)
undefined
app.add_exception_handler(AppError, app_error_handler)
undefined

Frontend Error Handling

前端错误处理

typescript
// api-client.ts
class ApiError extends Error {
  constructor(
    public code: string,
    public message: string,
    public statusCode: number,
    public details?: Record<string, unknown>
  ) {
    super(message);
  }
}

async function apiRequest<T>(url: string, options?: RequestInit): Promise<T> {
  const response = await fetch(url, options);

  if (!response.ok) {
    const body = await response.json();
    throw new ApiError(
      body.error.code,
      body.error.message,
      response.status,
      body.error.details
    );
  }

  return response.json();
}

// Usage with error handling
try {
  const user = await apiRequest('/api/users/123');
} catch (error) {
  if (error instanceof ApiError) {
    if (error.code === 'RESOURCE_NOT_FOUND') {
      showNotification('User not found');
    } else if (error.code === 'VALIDATION_ERROR') {
      showFormErrors(error.details.errors);
    }
  }
}
typescript
// api-client.ts
class ApiError extends Error {
  constructor(
    public code: string,
    public message: string,
    public statusCode: number,
    public details?: Record<string, unknown>
  ) {
    super(message);
  }
}

async function apiRequest<T>(url: string, options?: RequestInit): Promise<T> {
  const response = await fetch(url, options);

  if (!response.ok) {
    const body = await response.json();
    throw new ApiError(
      body.error.code,
      body.error.message,
      response.status,
      body.error.details
    );
  }

  return response.json();
}

// Usage with error handling
try {
  const user = await apiRequest('/api/users/123');
} catch (error) {
  if (error instanceof ApiError) {
    if (error.code === 'RESOURCE_NOT_FOUND') {
      showNotification('User not found');
    } else if (error.code === 'VALIDATION_ERROR') {
      showFormErrors(error.details.errors);
    }
  }
}

Best Practices

最佳实践

  1. Use error codes - Machine-readable, stable across versions
  2. Include request ID - Essential for debugging
  3. Log appropriately - Warn for operational, error for bugs
  4. Don't leak internals - Hide stack traces in production
  5. Be consistent - Same format everywhere
  1. 使用错误码 - 机器可读,跨版本稳定
  2. 包含请求ID - 调试的关键信息
  3. 合理记录日志 - 业务错误用警告级别,Bug用错误级别
  4. 不泄露内部信息 - 生产环境隐藏堆栈跟踪
  5. 保持一致性 - 所有场景使用相同的响应格式

Common Mistakes

常见误区

  • Returning stack traces to users
  • Generic "Something went wrong" messages
  • Not logging errors
  • Inconsistent error formats
  • Catching and swallowing errors
  • 向用户返回堆栈跟踪信息
  • 使用通用的“出问题了”提示信息
  • 不记录错误信息
  • 错误响应格式不一致
  • 捕获错误后直接忽略不处理