You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS最佳实践:如何避免在服务层处理HTTP特定错误?

NestJS 服务层与协议解耦的错误处理最佳实践

你的思路完全正确——服务层不应该耦合HTTP这类传输协议的细节,否则后续适配GRPC、MQTT等其他协议时,需要大量修改业务代码。下面是几种成熟的NestJS最佳实践方案:

1. 自定义业务异常 + 全局异常过滤器(推荐)

服务层抛出语义化的业务异常,由全局过滤器统一将异常转换为对应协议的错误响应,彻底实现业务逻辑与协议解耦。

步骤1:定义业务异常类

// src/common/exceptions/user-not-found.exception.ts
export class UserNotFoundException extends Error {
  constructor(public readonly userId: number) {
    super(`User with ID ${userId} not found`);
    this.name = 'UserNotFoundException';
  }
}

步骤2:服务层抛出业务异常

import { Injectable } from '@nestjs/common';
import { UserNotFoundException } from '../common/exceptions/user-not-found.exception';

@Injectable()
export class UserService {
  async remove(id: number): Promise<Partial<User>> {
    const user = await this.userRepository.findOne({ where: { id } });
    if (!user) {
      // 抛出业务异常,而非HTTP相关错误
      throw new UserNotFoundException(id);
    }
    await this.userRepository.remove(user);
    return { id };
  }
}

步骤3:全局异常过滤器处理异常

// src/common/filters/business-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from '@nestjs/common';
import { Response } from 'express';
import { UserNotFoundException } from '../exceptions/user-not-found.exception';

@Catch(UserNotFoundException)
export class BusinessExceptionFilter implements ExceptionFilter {
  catch(exception: UserNotFoundException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    
    response.status(HttpStatus.NOT_FOUND).json({
      statusCode: HttpStatus.NOT_FOUND,
      message: exception.message,
      error: 'Not Found'
    });
  }
}

步骤4:注册全局过滤器

在main.ts中注册过滤器,全局生效:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { BusinessExceptionFilter } from './common/filters/business-exception.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new BusinessExceptionFilter());
  await app.listen(3000);
}
bootstrap();

这种方案的优势:

  • 服务层只关注业务逻辑,异常语义清晰
  • 不同协议适配器(HTTP控制器、GRPC拦截器等)可各自处理异常,适配灵活
  • 符合NestJS的AOP(面向切面)设计理念

2. 通用Result类型封装返回值

如果你不习惯用异常处理业务错误,可以定义通用的Result类型,明确区分成功与失败状态:

定义Result类型

// src/common/types/result.type.ts
type SuccessResult<T> = {
  success: true;
  data: T;
};

type ErrorResult = {
  success: false;
  errorCode: number;
  errorMessage: string;
};

export type Result<T> = SuccessResult<T> | ErrorResult;

服务层返回Result对象

async remove(id: number): Promise<Result<Partial<User>>> {
  const user = await this.userRepository.findOne({ where: { id } });
  if (!user) {
    return {
      success: false,
      errorCode: 404,
      errorMessage: 'user not found'
    };
  }
  await this.userRepository.remove(user);
  return {
    success: true,
    data: { id }
  };
}

控制器处理Result

// user.controller.ts
import { Controller, Delete, HttpException, Param } from '@nestjs/common';
import { UserService } from './user.service';
import { Result } from '../common/types/result.type';

@Controller('users')
export class UserController {
  constructor(private userService: UserService) {}

  @Delete(':id')
  async remove(@Param('id') id: number) {
    const result = await this.userService.remove(id);
    if (!result.success) {
      throw new HttpException(result.errorMessage, result.errorCode);
    }
    return result.data;
  }
}

这种方案更偏向“错误优先”的返回风格,适合对异常使用较为谨慎的场景。

3. 通用业务异常 + 统一映射

如果需要处理多种业务错误,可以定义通用的业务异常类,通过错误码映射到不同的HTTP状态:

定义通用业务异常

// src/common/exceptions/business-error.exception.ts
export class BusinessErrorException extends Error {
  constructor(public readonly errorCode: number, message: string) {
    super(message);
    this.name = 'BusinessErrorException';
  }
}

服务层抛出通用异常

if (!user) {
  throw new BusinessErrorException(404, 'user not found');
}

全局过滤器统一映射

// src/common/filters/business-error.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { Response } from 'express';
import { BusinessErrorException } from '../exceptions/business-error.exception';

@Catch(BusinessErrorException)
export class BusinessErrorFilter implements ExceptionFilter {
  catch(exception: BusinessErrorException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    
    response.status(exception.errorCode).json({
      statusCode: exception.errorCode,
      message: exception.message
    });
  }
}

核心原则

无论选择哪种方案,核心都是:服务层专注业务逻辑实现,协议相关的错误处理交给适配器层(控制器、拦截器、过滤器),确保业务代码的可复用性和扩展性。

内容的提问来源于stack exchange,提问作者Christian LSANGOLA

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 20:30:43