Exception filter
例外處理器 (Exception filter) 用來捕捉程序中發生的錯誤,也就是一般常見的 catch error 方法。
NestJS 的全域錯誤處理層只會捕捉透過 HttpException 建立或繼承出來的實例,其他類型的 error 則需要自己設計 filter 元件,否則只會回傳 Internal server error:
{ "statusCode": 500, "message": "Internal server error"}標準 exception
throw 一個 HttpException 實例,建構函式要帶入自訂訊息和狀態碼:
@Get('test-standard-exception')getStandardException() { throw new HttpException('這是標準的 exception', HttpStatus.BAD_REQUEST);}得到:
{ "statusCode": 400, "message": "這是標準的 exception"}自訂訊息也可以改為傳入物件,來蓋掉預設的格式:
@Get('test-standard-exception')getStandardException() { const customExceptionObj = { code: HttpStatus.BAD_REQUEST, msg: '這是自訂格式的標準 exception', };
throw new HttpException(customExceptionObj, HttpStatus.BAD_REQUEST);}得到:
{ "code": 400, "msg": "這是自訂格式的標準 exception"}建構函式帶入的代號也會作為 HTTP 回應物件的狀態碼,與自動產生 body 時的 statusCode 會是一致的,除非在自訂訊息中故意複寫一個不同的代碼。
內建 exception
NestJS 有根據狀態碼的語意封裝好的 exception,例如可以實例化 UnauthorizedException,這樣產生的回應就會自動帶入這個 401 狀態碼 :
@Get('test-built-in-exception')getBuiltInException() { throw new UnauthorizedException('這是內建的 unauthorized exception');}自動產生的回應物件多了 error 這個欄位描述這個 exception:
{ "message": "這是內建的 unauthorized exception", "error": "Unauthorized", "statusCode": 401}一樣可以自訂格式:
@Get('test-built-in-exception')getBuiltInException() { const customBody = { code: HttpStatus.UNAUTHORIZED, msg: '這是自訂格式的 unauthorized exception', };
throw new UnauthorizedException(customBody);}{ "code": 401, "msg": "這是自訂格式的 unauthorized exception"}自訂 exception
需要統一格式時也可以自定義一個繼承某個 exception 的類別:
export class CustomException extends HttpException { constructor() { super('自訂 exception 的錯誤', HttpStatus.INTERNAL_SERVER_ERROR); }}@Get('test-custom-exception')getCustomException() { throw new CustomException();}filter
錯誤處理器也可以自己生成:
nest g filter filter/httpCLI 會生成一個帶有 @Catch 裝飾器的類別,泛型 T 再改寫成想要捕捉的類型:
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
@Catch()export class MyHttpFilter<T> implements ExceptionFilter { catch(exception: T, host: ArgumentsHost) {}}假設要做一個捕捉 HttpException 的 filter,就會在 @Catch 傳入 HttpException,並拓展泛型 T,確保 exception: T 能夠存取 HttpException 的屬性和方法:
@Catch(HttpException)export class MyHttpFilter<T extends HttpException> implements ExceptionFilter { catch(exception: T, host: ArgumentsHost) {}}@Catch() 用來指定要捕捉的錯誤類別,本質上是 try&catch 語法,所以大部分的錯誤都可以填入裝飾器:
// 同時處理多個 HttpException 類型的 exception@Catch( BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException)
// JS 標準錯誤@Catch(ReferenceError)
// 全部的錯誤都捕捉,此時 exception: unknown@Catch()ArgumentsHost
host 定義了一些方法來處理不同網路架構的介面 (interface),HTTP、RPC、WebSocket,這些架構的參數內容不同:
catch(exception: T, host: ArgumentsHost) { // getType 可以知道是什麼架構,並根據對應架構撰寫邏輯 console.log(host.getType()); // 'http' | 'rpc' | 'ws'
// 使用 switchToHttp 轉換架構內容,並指定型別為 HttpArgumentsHost const httpCtx: HttpArgumentsHost = host.switchToHttp();
// 取出 response 並指定為 Express 的 Response const response = httpCtx.getResponse<Response>(); const message = exception.getResponse(); const statusCode = exception.getStatus();
const responseBody = { code: statusCode, message: message, timestamp: new Date().toISOString(), };
// 同 Express 的 router,接上 .json 直接拋出回應 response.status(statusCode).json(responseBody);}ArgumentsHost 的定義檔裡面包含各架構的參數,像 HttpArgumentsHost 就是很標準的 HTTP 物件與函式:
export interface HttpArgumentsHost { /** * Returns the in-flight `request` object. */ getRequest<T = any>(): T; /** * Returns the in-flight `response` object. */ getResponse<T = any>(): T; getNext<T = any>(): T;}ctx.getResponse 是取得 HTTP 物件,exception.getResponse 是取得 exception 的回應內容,也就是上面在 throw 各種 exception 實例時傳入建構函式的訊息 。
部分套用
使用 @UseFilter 標注在 controller 的方法上就可以套用指定的 filter:
@UseFilters(MyHttpFilter)@Get('test-my-http-filter')getHttpFilterException() { throw new UnprocessableEntityException('這是自訂 filter 的 422 錯誤');}也可以標注在 @Controller 上,讓整個 controller 都套用:
@UseFilters(MyHttpFilter)@Controller()export class AppController { //...}全域套用
在根模組進行注入,有多個自訂 filter 需要套用時仍然共用 APP_FILTER 這個 token,如果多個 filter 都可以捕捉到同類型的錯誤,會依照這裡的陣列順序套用:
import { APP_FILTER } from '@nest/core';
@Module({ controllers: [AppController], providers: [ { provide: APP_FILTER, useClass: MyHttpFilter, }, { provide: APP_FILTER, useClass: BadRequestFilter, }, ],})export class AppModule {}或是在啟動程序裡面呼叫 useGlobalFilters 並傳入一個 filter 的實例:
async function bootstrap() { const app = await NestFactory.create(AppModule); // 建立實例 app.useGlobalFilters(new MyHttpFilter()); await app.listen(process.env.PORT ?? 3000);}格式修正
目前的套用方式會得到這樣的回應:
{ "code": 422, "message": { "message": "這是自訂 filter 的 422 錯誤", "error": "Unprocessable Entity", "statusCode": 422 }, "timestamp": "2025-05-05T04:05:51.714Z"}外層的 message 被塞入的是內建 exception 回應物件,需要調整 exception.getResponse() 輸出的內容:
const message = (() => { const res = exception.getResponse();
if (typeof res === 'string') { return res; }
// 暫時斷言型別 return (res as { message: string }).message;})();這樣 throw 時傳入建構函式的字串會進行上面的判斷,字串會作為 message 的值輸出,傳入物件就取出物件裡面的 message:
@Get('test-http-filter')getHttpFilterException() { // 傳入字串 throw new UnprocessableEntityException('這是自訂格式的 422 錯誤');}{ "code": 422, "message": "這是自訂格式的 422 錯誤", "timestamp": "2025-05-05T07:05:36.365Z"}不傳任何東西時會自動帶入內建 exception 回應物件,所以也適用上面的斷言:
@Get('test-http-filter')getHttpFilterException() { // 不傳任何東西 throw new UnprocessableEntityException();}此時就會帶出內建 422 exception 的 message:
{ "code": 422, "message": "Unprocessable Entity", "timestamp": "2025-05-05T07:00:13.501Z"}小結
內建 exception 只要訂好回應格式,已經能應付大多情境。
接入外部服務或是 ValidationPipe 產生的報錯,也需要自訂統一格式的話,就會需要自己實作 filter。