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/http
CLI 會生成一個帶有 @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。