在Nestjs世界裡,有一層Exception Layer,專門負責捕捉Exception,並適當的回應給Client。
預設情況下,所有未經定義的Exception(非HttpException或未繼承HttpException的class),都會被Global Exception Filter捕捉處理,並返回一段JSON訊息給Client。
{
"statusCode": 500,
"message": "Internal server error"
}
import { Controller, Get, Post, Request, Response, Param, Next, HttpStatus, Body, HttpException } from '@nestjs/common';
import { CreateUserDTO } from './DTO/create-users.dto';
import { UsersService } from '../Users/Services/users.service';
import { ProductsService } from '../Products/Services/products.service';
@Controller()
export class UsersController {
//依賴注入,建議要使用,這是低耦合作法
//注入ProductsService
constructor(private userService: UsersService, private productsService: ProductsService) { }
@Get('users')
//使用Express的參數
async getAllUsers( @Request() req, @Response() res, @Next() next) {
//Promise 有then catch方法可以調用
await this.userService.getAllUsers()
.then((users) => {
//多種Http的Status可以使用
res.status(HttpStatus.OK).json(users);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
@Get('users/:id')
//使用Express的參數
//@Param('id')可以直接抓id參數
async getUser( @Response() res, @Param('id') id) {
//+id ,+符號可以直接把string 轉換成number
await this.userService.getUser(+id)
.then((user) => {
res.status(HttpStatus.OK).json(user);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
@Post('users')
async addUser( @Response() res, @Body() createUserDTO: CreateUserDTO) {
//使用Rx.js,所以回傳可以做更多資料流的處理
await this.userService.addUser(createUserDTO).subscribe((users) => {
res.status(HttpStatus.OK).json(users);
})
}
//測試ProductsService是否可以正常使用
@Get('testProducts')
//使用Express的參數
async testGetAllProducts( @Request() req, @Response() res, @Next() next) {
//Promise 有then catch方法可以調用
await this.productsService.getAllProducts()
.then((products) => {
//多種Http的Status可以使用
res.status(HttpStatus.OK).json(products);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
//新增/getException使用HttpException
@Get('getException')
async getException(@Request() req, @Response() res, @Next() next){
//直接拋錯,Nestjs有ExceptionsHandler 會主動捕捉錯誤。
throw new Error('error');
}
}
說明:Nestjs有一層Exception layer,有一隻ExceptionsHandler會去執行捕捉錯誤。
說明:GET回傳結果為Nestjs預設的錯誤回應。
import { Controller, Get, Post, Request, Response, Param, Next, HttpStatus, Body, HttpException } from '@nestjs/common';
import { CreateUserDTO } from './DTO/create-users.dto';
import { UsersService } from '../Users/Services/users.service';
import { ProductsService } from '../Products/Services/products.service';
@Controller()
export class UsersController {
//依賴注入,建議要使用,這是低耦合作法
//注入ProductsService
constructor(private userService: UsersService, private productsService: ProductsService) { }
@Get('users')
//使用Express的參數
async getAllUsers( @Request() req, @Response() res, @Next() next) {
//Promise 有then catch方法可以調用
await this.userService.getAllUsers()
.then((users) => {
//多種Http的Status可以使用
res.status(HttpStatus.OK).json(users);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
@Get('users/:id')
//使用Express的參數
//@Param('id')可以直接抓id參數
async getUser( @Response() res, @Param('id') id) {
//+id ,+符號可以直接把string 轉換成number
await this.userService.getUser(+id)
.then((user) => {
res.status(HttpStatus.OK).json(user);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
@Post('users')
async addUser( @Response() res, @Body() createUserDTO: CreateUserDTO) {
//使用Rx.js,所以回傳可以做更多資料流的處理
await this.userService.addUser(createUserDTO).subscribe((users) => {
res.status(HttpStatus.OK).json(users);
})
}
//測試ProductsService是否可以正常使用
@Get('testProducts')
//使用Express的參數
async testGetAllProducts( @Request() req, @Response() res, @Next() next) {
//Promise 有then catch方法可以調用
await this.productsService.getAllProducts()
.then((products) => {
//多種Http的Status可以使用
res.status(HttpStatus.OK).json(products);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
//新增/getException使用HttpException
@Get('getException')
async getException(@Request() req, @Response() res, @Next() next){
/*HttpException接受兩個參數,第一個是string或object,如果是object
,會override response第二個參數是HTTP status*/
throw new HttpException('禁止訪問',HttpStatus.FORBIDDEN);
}
}
說明:throw new HttpException(),使用nestjs的API,做更細緻的處理。
說明:如此我們就客製化了回應的錯誤訊息和狀態碼。
cd src/modules/Shared & mkdir ExceptionFilters
import { HttpException } from "@nestjs/core/exceptions/http-exception";
import { HttpStatus } from "@nestjs/common";
export class CustomForbiddenException extends HttpException {
constructor() {
super('禁止訪問', HttpStatus.FORBIDDEN);
}
}
說明:ForbiddenException extends HttpException,所有特性都會跟HttpException一樣。
import { Controller, Get, Post, Request, Response, Param, Next, HttpStatus, Body, HttpException } from '@nestjs/common';
import { CreateUserDTO } from './DTO/create-users.dto';
import { UsersService } from '../Users/Services/users.service';
import { ProductsService } from '../Products/Services/products.service';
import { CustomForbiddenException } from '../Shared/ExceptionFilters/forbidden.exception';
@Controller()
export class UsersController {
//依賴注入,建議要使用,這是低耦合作法
//注入ProductsService
constructor(private userService: UsersService, private productsService: ProductsService) { }
@Get('users')
//使用Express的參數
async getAllUsers( @Request() req, @Response() res, @Next() next) {
//Promise 有then catch方法可以調用
await this.userService.getAllUsers()
.then((users) => {
//多種Http的Status可以使用
res.status(HttpStatus.OK).json(users);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
@Get('users/:id')
//使用Express的參數
//@Param('id')可以直接抓id參數
async getUser( @Response() res, @Param('id') id) {
//+id ,+符號可以直接把string 轉換成number
await this.userService.getUser(+id)
.then((user) => {
res.status(HttpStatus.OK).json(user);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
@Post('users')
async addUser( @Response() res, @Body() createUserDTO: CreateUserDTO) {
//使用Rx.js,所以回傳可以做更多資料流的處理
await this.userService.addUser(createUserDTO).subscribe((users) => {
res.status(HttpStatus.OK).json(users);
})
}
//測試ProductsService是否可以正常使用
@Get('testProducts')
//使用Express的參數
async testGetAllProducts( @Request() req, @Response() res, @Next() next) {
//Promise 有then catch方法可以調用
await this.productsService.getAllProducts()
.then((products) => {
//多種Http的Status可以使用
res.status(HttpStatus.OK).json(products);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
//新增/getException使用HttpException
@Get('getException')
async getException(@Request() req, @Response() res, @Next() next){
//使用CustomForbiddenException
throw new CustomForbiddenException();
}
}
說明:再使用Postman對http://localhost:3000/getException 做GET請求,結果會跟第4點一樣。
8.1 在src/modules/Shared/ExceptionFilters下新增http-exception.filter.ts ,並寫些程式碼如下。
src/modules/Shared/ExceptionFilters/http-exception.filter.ts
import { ExceptionFilter, Catch } from '@nestjs/common';
import { HttpException } from '@nestjs/core';
//記得使用Catch裝飾器,可以傳入無數個參數。
@Catch(HttpException)
//建議要implements ExceptionFilter,實作它的interface
export class HttpExceptionFilter implements ExceptionFilter {
//傳入HttpException
catch(exception: HttpException, response) {
const status = exception.getStatus();
const message = '我是Exception Log Message';
//做log動作
console.log(`exception status:`, status);
console.log(`exception message:`, message);
//調整response的json內容
response.status(status).json({
statusCode: status,
message: message,
});
}
}
說明:@Catch()裝飾器會連接metadata到Exception Filter,告訴Nestjs,Filter會去尋找HttpException。
src/modules/Users/user.controller.ts
import { Controller, Get, Post, Request, Response, Param, Next, HttpStatus, Body, HttpException, UseFilters } from '@nestjs/common';
import { CreateUserDTO } from './DTO/create-users.dto';
import { UsersService } from '../Users/Services/users.service';
import { ProductsService } from '../Products/Services/products.service';
import { CustomForbiddenException } from '../Shared/ExceptionFilters/forbidden.exception';
import { HttpExceptionFilter } from '../Shared/ExceptionFilters/http-exception.filter';
@Controller()
export class UsersController {
//依賴注入,建議要使用,這是低耦合作法
//注入ProductsService
constructor(private userService: UsersService, private productsService: ProductsService) { }
@Get('users')
//使用Express的參數
async getAllUsers( @Request() req, @Response() res, @Next() next) {
//Promise 有then catch方法可以調用
await this.userService.getAllUsers()
.then((users) => {
//多種Http的Status可以使用
res.status(HttpStatus.OK).json(users);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
@Get('users/:id')
//使用Express的參數
//@Param('id')可以直接抓id參數
async getUser( @Response() res, @Param('id') id) {
//+id ,+符號可以直接把string 轉換成number
await this.userService.getUser(+id)
.then((user) => {
res.status(HttpStatus.OK).json(user);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
@Post('users')
async addUser( @Response() res, @Body() createUserDTO: CreateUserDTO) {
//使用Rx.js,所以回傳可以做更多資料流的處理
await this.userService.addUser(createUserDTO).subscribe((users) => {
res.status(HttpStatus.OK).json(users);
})
}
//測試ProductsService是否可以正常使用
@Get('testProducts')
//使用Express的參數
async testGetAllProducts( @Request() req, @Response() res, @Next() next) {
//Promise 有then catch方法可以調用
await this.productsService.getAllProducts()
.then((products) => {
//多種Http的Status可以使用
res.status(HttpStatus.OK).json(products);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
//新增/getException使用HttpException
@Get('getException')
//使用HttpExceptionFilter
@UseFilters(new HttpExceptionFilter())
async getException(@Request() req, @Response() res, @Next() next){
//使用CustomForbiddenException
throw new CustomForbiddenException();
}
}
說明:Controller呼叫一下@UseFilters()裝飾器,傳入HttpExceptionFilter,如此CustomForbiddenException的行為會被控制改寫。
[Nest] 16148 - 2017-12-8 13:00:26 [NestFactory] Starting Nest application...
[Nest] 16148 - 2017-12-8 13:00:26 [InstanceLoader] ApplicationModule dependencies initialized +6ms
[Nest] 16148 - 2017-12-8 13:00:26 [InstanceLoader] UsersModule dependencies initialized +3ms
[Nest] 16148 - 2017-12-8 13:00:26 [InstanceLoader] ProductsModule dependencies initialized +1ms
[Nest] 16148 - 2017-12-8 13:00:26 [RoutesResolver] UsersController {/}: +54ms
[Nest] 16148 - 2017-12-8 13:00:26 [RouterExplorer] Mapped {/users, GET} route +2ms
[Nest] 16148 - 2017-12-8 13:00:26 [RouterExplorer] Mapped {/users/:id, GET} route +1ms
[Nest] 16148 - 2017-12-8 13:00:26 [RouterExplorer] Mapped {/users, POST} route +1ms
[Nest] 16148 - 2017-12-8 13:00:26 [RouterExplorer] Mapped {/testProducts, GET} route +1ms
[Nest] 16148 - 2017-12-8 13:00:26 [RouterExplorer] Mapped {/getException, GET} route +2ms
[Nest] 16148 - 2017-12-8 13:00:26 [RoutesResolver] ProductsController {/products}: +3ms
[Nest] 16148 - 2017-12-8 13:00:26 [RouterExplorer] Mapped {/, GET} route +3ms
[Nest] 16148 - 2017-12-8 13:00:26 [RouterExplorer] Mapped {/:id, GET} route +12ms
[Nest] 16148 - 2017-12-8 13:00:26 [RouterExplorer] Mapped {/, POST} route +3ms
[Nest] 16148 - 2017-12-8 13:00:26 [NestApplication] Nest application successfully started +1ms
Application based on Express is listening on port 3000
來自根模組的參數
執行middleware...
我是簡單的Middleware
[Nest] 16148 - 2017-12-8 13:00:29 [HttpException] DEPRECATED! Since version [4.3.2] HttpException class was moved to the @nestjs/common package!
exception status: 403
exception message: 我是Exception Log Message
說明:console有打印log資訊,在這邊是以console.log()替代log操作。
9.2 Postman結果如下。
說明:json回應有被改寫,Exception Filter具有override功用。
@Controller()
@UseFilters(new HttpExceptionFilter())
export class UsersController {...省略}
src/modules/app.module.ts
import { Module, RequestMethod, UseFilters } from '@nestjs/common';
import { UsersController } from './Users/users.controller';
import { UsersService } from './Users/Services/users.service';
import { UsersModule } from './Users/users.module';
import { LoggerMiddleware } from './Shared/Middlewares/logger.middleware';
import { SimpleMiddleware } from './Shared/Middlewares/simple.middleware';
import { NestModule, MiddlewaresConsumer } from '@nestjs/common/interfaces';
import { ProductsController } from './Products/products.controller';
import { HttpExceptionFilter } from './Shared/ExceptionFilters/http-exception.filter';
@Module({
modules: [UsersModule]
})
//load HttpExceptionFilter
@UseFilters(new HttpExceptionFilter())
//NestModule本身是個Interface,建議要implements。
export class ApplicationModule implements NestModule {
configure(consumer: MiddlewaresConsumer): void {
//apply、forRoute方法允許傳入多個參數
consumer.apply([LoggerMiddleware,SimpleMiddleware])
//with方法可以傳入參數到middleware
.with('來自根模組的參數')
.forRoutes(
//load進Controllers
UsersController,
ProductsController
)
}
}
大功告成,這樣對Exception Filter就有更多的了解,真是神好用的Filter。
程式碼在github