├── node_modules
├── prisma
│ ├── migrations
│ ├── schema.prisma
│ └── seed.ts
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── main.ts
│ ├── articles
│ └── prisma
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── README.md
├── .env
├── docker-compose.yml
├── nest-cli.json
├── package-lock.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json

此存儲(chǔ)庫中值得注意的文件和目錄包括:

請(qǐng)注意,有關(guān)這些組件的更多詳細(xì)信息,請(qǐng)閱讀本教程系列的第一部分。

直接檢測(cè)并引發(fā)異常

本節(jié)將指導(dǎo)您如何在應(yīng)用程序代碼中直接觸發(fā)異常。您將針對(duì)一個(gè)終端節(jié)點(diǎn)的問題進(jìn)行修復(fù)。目前,若向該終端節(jié)點(diǎn)傳遞無效的值,它不會(huì)返回 HTTP 狀態(tài)碼,而是直接返回錯(cuò)誤信息。

例如,對(duì)于 GET 請(qǐng)求 /articles/:id,如果傳遞的 ID(如 234235)不存在,則會(huì)出現(xiàn)問題。

請(qǐng)求不存在的文章會(huì)返回 HTTP 200

要解決此問題,您必須在 findOnearticles.controller.ts 文件中的方法做出更改。如果文章不存在,您應(yīng)該拋出一個(gè) NotFoundException,這是 NestJS 提供的內(nèi)置異常。

更新 findOnearticles.controller.ts 文件中的相關(guān)方法。

// src/articles/articles.controller.ts

import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
NotFoundException,
} from '@nestjs/common';

@Get(':id')
@ApiOkResponse({ type: ArticleEntity })
findOne(@Param('id') id: string) {
return this.articlesService.findOne(+id);
async findOne(@Param('id') id: string) {
const article = await this.articlesService.findOne(+id);
if (!article) {
throw new NotFoundException(Article with ${id} does not exist.); } return article; }

如果您再次發(fā)出相同的請(qǐng)求,您應(yīng)該會(huì)收到一條用戶友好的錯(cuò)誤消息:

請(qǐng)求不存在的文章會(huì)返回 HTTP 404

使用異常篩選器處理異常

專用例外層的優(yōu)點(diǎn)

在上一節(jié)中,您學(xué)會(huì)了如何檢測(cè)錯(cuò)誤狀態(tài)并手動(dòng)拋出異常。然而,在許多情況下,應(yīng)用程序代碼會(huì)自動(dòng)生成異常。此時(shí),您應(yīng)該捕獲這些異常并向用戶返回合適的 HTTP 錯(cuò)誤響應(yīng)。

盡管您可以在每個(gè)控制器中單獨(dú)處理異常,但這并非最佳實(shí)踐,原因如下:

為了解決這些問題,NestJS 提供了一個(gè)異常層,用于處理整個(gè)應(yīng)用程序中未捕獲的異常。在 NestJS 中,您可以創(chuàng)建異常過濾器來定義如何響應(yīng)應(yīng)用程序內(nèi)部拋出的不同類型的異常。

NestJS 全局異常過濾器

NestJS 支持全局異常過濾器,它可以捕獲所有未處理的異常。為了理解全局異常過濾器的工作原理,我們來看一個(gè)示例。請(qǐng)向?/articles?終端節(jié)點(diǎn)發(fā)送 POST 請(qǐng)求,并觀察其響應(yīng)。

{
"title": "Let’s build a REST API with NestJS and Prisma.",
"description": "NestJS Series announcement.",
"body": "NestJS is one of the hottest Node.js frameworks around. In this series, you will learn how to build a backend REST API with NestJS, Prisma, PostgreSQL and Swagger.",
"published": true
}

第一個(gè)請(qǐng)求將會(huì)成功,但第二個(gè)請(qǐng)求會(huì)因?yàn)槟呀?jīng)創(chuàng)建了具有相同標(biāo)題(title)的文章而失敗。您將會(huì)收到一個(gè)與標(biāo)題相關(guān)的錯(cuò)誤。

{
"statusCode": 500,
"message": "Internal server error"
}

如果您查看運(yùn)行 NestJS 服務(wù)器的終端窗口,您應(yīng)該會(huì)看到以下錯(cuò)誤:

[Nest] 6803  - 12/06/2022, 3:25:40 PM   ERROR [ExceptionsHandler]
Invalid this.prisma.article.create() invocation in /Users/tasinishmam/my-code/median/src/articles/articles.service.ts:11:32 8 constructor(private prisma: PrismaService) {} 9 10 create(createArticleDto: CreateArticleDto) { → 11 return this.prisma.article.create( Unique constraint failed on the fields: (title) Error: Invalid this.prisma.article.create() invocation in /Users/tasinishmam/my-code/median/src/articles/articles.service.ts:11:32 8 constructor(private prisma: PrismaService) {} 9 10 create(createArticleDto: CreateArticleDto) { → 11 return this.prisma.article.create( Unique constraint failed on the fields: (title)

從日志中,您可以觀察到 Prisma Client 因?yàn)槟硞€(gè)字段觸發(fā)了唯一性約束驗(yàn)證錯(cuò)誤,該字段在 Prisma 架構(gòu)中被標(biāo)記為唯一。引發(fā)的異常類型是?PrismaClientKnownRequestError,并且這個(gè)異常在 Prisma 的命名空間級(jí)別被導(dǎo)出。具體地說,是因?yàn)?title?字段的唯一性約束導(dǎo)致了這個(gè)問題。

由于這個(gè)異常不是由您的應(yīng)用程序直接捕獲和處理的,因此它會(huì)被內(nèi)置的全局異常過濾器自動(dòng)捕獲。然而,這個(gè)全局異常過濾器默認(rèn)生成的是 HTTP “Internal Server Error”(500)響應(yīng)。

創(chuàng)建手動(dòng)異常篩選條件

在本節(jié)中,您將創(chuàng)建一個(gè)自定義的異常過濾器來處理上述類型的異常。這個(gè)自定義過濾器將能夠捕獲所有類型的異常,并且能夠?yàn)橛脩舴祷厍逦⒂押玫腻e(cuò)誤消息,特別是針對(duì)?PrismaClientKnownRequestError?這類異常。

首先使用 Nest CLI 生成過濾器類:

npx nest generate filter prisma-client-exception

這將創(chuàng)建一個(gè)名為 src/prisma-client-exception.filter.ts 的新文件。

// src/prisma-client-exception.filter.ts

import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';

@Catch()
export class PrismaClientExceptionFilter<T> implements ExceptionFilter {
catch(exception: T, host: ArgumentsHost) {}
}

注意:請(qǐng)注意,已創(chuàng)建了一個(gè)名為 prisma-client-exception.filter.spec.ts 的測(cè)試文件。目前,您可以暫時(shí)忽略這個(gè)文件。

由于 prisma-client-exception.filter.ts 中的方法實(shí)現(xiàn)為空,您可能會(huì)收到來自 ESLint 的錯(cuò)誤提示。為了解決這個(gè)問題,您需要更新該方法以實(shí)現(xiàn)一個(gè)捕獲 Prisma 客戶端異常的過濾器。這里假設(shè)您已經(jīng)有了相關(guān)的實(shí)現(xiàn)計(jì)劃或代碼框架。

// src/prisma-client-exception.filter.ts

import { ArgumentsHost, Catch } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import { Prisma } from '@prisma/client';

@Catch(Prisma.PrismaClientKnownRequestError) // 1
export class PrismaClientExceptionFilter extends BaseExceptionFilter { // 2
catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) {
console.error(exception.message); // 3

// default 500 error code
super.catch(exception, host);
}
}

在這里,您進(jìn)行了以下更改:

  1. 為了確保此過濾器能夠捕獲?PrismaClientKnownRequestError?類型的異常,您將其添加到了?@Catch(PrismaClientKnownRequestError)?裝飾器中。
  2. 異常過濾器擴(kuò)展了 NestJS 核心包中的 BaseExceptionFilter 類,該類為向用戶返回“Internal server error”響應(yīng)的方法提供了默認(rèn)實(shí)現(xiàn)。您可以在 NestJS 文檔中了解更多相關(guān)信息。
  3. 您添加了一個(gè) console.error 語句,用于將錯(cuò)誤消息記錄到控制臺(tái),這對(duì)于調(diào)試目的非常有用。

由于 Prisma 會(huì)引發(fā)許多不同類型的錯(cuò)誤,您需要弄清楚如何從捕獲的異常中提取錯(cuò)誤代碼。異常對(duì)象具有一個(gè)包含錯(cuò)誤代碼的屬性,您可以在 Prisma 錯(cuò)誤消息參考中找到所有錯(cuò)誤代碼的列表。

您要查找的錯(cuò)誤代碼是?P2002,它通常發(fā)生在唯一約束沖突中。接下來,您將更新?catch?方法,以便在捕獲到此錯(cuò)誤時(shí)引發(fā)一個(gè)帶有 HTTP 409 Conflict 狀態(tài)碼的響應(yīng),并向用戶提供自定義錯(cuò)誤消息。

更新您的異常過濾器實(shí)現(xiàn),如下所示:

//src/prisma-client-exception.filter.ts

import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import { Prisma } from '@prisma/client';
import { Response } from 'express';

@Catch(Prisma.PrismaClientKnownRequestError)
export class PrismaClientExceptionFilter extends BaseExceptionFilter {
catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) {
console.error(exception.message);
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const message = exception.message.replace(/\n/g, '');

switch (exception.code) {
case 'P2002': {
const status = HttpStatus.CONFLICT;
response.status(status).json({
statusCode: status,
message: message,
});
break;
}
default:
// default 500 error code
super.catch(exception, host);
break;
}
}
}

在這里,您將訪問底層框架對(duì)象并直接修改響應(yīng)。默認(rèn)情況下,express 是 NestJS 在后臺(tái)使用的 HTTP 框架。對(duì)于除 之外的任何異常代碼,您將發(fā)送默認(rèn)的 “Internal server error” 響應(yīng)。

注意:對(duì)于生產(chǎn)應(yīng)用程序,請(qǐng)注意不要在錯(cuò)誤消息中向用戶泄露任何敏感信息。

將異常篩選器應(yīng)用于應(yīng)用程序

要使?PrismaClientExceptionFilter?生效,您需要將其應(yīng)用于適當(dāng)?shù)姆秶.惓_^濾器的應(yīng)用范圍可以是單個(gè)路由(方法級(jí))、整個(gè)控制器(控制器級(jí))或整個(gè)應(yīng)用程序(全局級(jí))。

為了將異常過濾器應(yīng)用于整個(gè)應(yīng)用程序,請(qǐng)更新 main.ts 文件。

// src/main.ts

import { HttpAdapterHost, NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { PrismaClientExceptionFilter } from './prisma-client-exception.filter';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

const config = new DocumentBuilder()
.setTitle('Median')
.setDescription('The Median API description')
.setVersion('0.1')
.build();

const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);

const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new PrismaClientExceptionFilter(httpAdapter));

await app.listen(3000);
}
bootstrap();

現(xiàn)在,嘗試向 /articles 終端節(jié)點(diǎn)再次發(fā)出相同的 POST 請(qǐng)求。

{
"title": "Let’s build a REST API with NestJS and Prisma.",
"description": "NestJS Series announcement.",
"body": "NestJS is one of the hottest Node.js frameworks around. In this series, you will learn how to build a backend REST API with NestJS, Prisma, PostgreSQL and Swagger.",
"published": true
}

這次,您將收到一個(gè)更用戶友好的錯(cuò)誤消息:

{
"statusCode": 409,
"message": "Invalid this.prisma.article.create() invocation in /Users/tasinishmam/my-code/median/src/articles/articles.service.ts:11:32 8 constructor(private prisma: PrismaService) {} 9 10 create(createArticleDto: CreateArticleDto) {→ 11 return this.prisma.article.create(Unique constraint failed on the fields: (title)" }

由于?PrismaClientExceptionFilter?是一個(gè)全局過濾器,因此它能夠?yàn)閼?yīng)用程序中的所有路由處理?PrismaClientKnownRequestError?這種特定類型的錯(cuò)誤。

我建議您進(jìn)一步擴(kuò)展異常過濾器的實(shí)現(xiàn),以處理其他類型的錯(cuò)誤。例如,您可以添加一個(gè)分支來處理錯(cuò)誤代碼 P2025,這個(gè)錯(cuò)誤代碼通常在數(shù)據(jù)庫中找不到記錄時(shí)出現(xiàn)。對(duì)于這種情況,您應(yīng)該返回 HttpStatus.NOT_FOUND 狀態(tài)碼。這對(duì)于 PATCH /articles/:id 和 DELETE /articles/:id 端點(diǎn)特別有用,因?yàn)檫@些操作通常依賴于特定記錄的存在。

使用nest js-prime包處理 Prisma 異常

到目前為止,您已經(jīng)了解了在 NestJS 應(yīng)用程序中手動(dòng)處理 Prisma 異常的不同技術(shù)。有一個(gè)用于將 Prisma 與 NestJS 一起使用的專用包,稱為nestjs-prisma您還可以使用它來處理 Prisma 異常。此包是一個(gè)很好的考慮選擇,因?yàn)樗鼊h除了大量樣板代碼。

有關(guān)如何安裝和使用 nestjs-prisma 包的詳細(xì)說明,請(qǐng)參考其官方文檔。使用此包時(shí),您無需手動(dòng)創(chuàng)建與 Prisma 相關(guān)的單獨(dú)模塊和服務(wù),因?yàn)檫@些都會(huì)由包自動(dòng)為您生成。

在?nestjs-prisma?文檔的“Exception Filter”部分,您可以了解到如何使用該包來處理 Prisma 異常。在本教程的后續(xù)章節(jié)中,我們將更深入地介紹這個(gè)軟件包。

總結(jié)和結(jié)束語

祝賀!在本教程中,您獲取了一個(gè)現(xiàn)有的 NestJS 應(yīng)用程序,并學(xué)習(xí)了如何集成錯(cuò)誤處理。您學(xué)習(xí)了兩種不同的錯(cuò)誤處理方法:直接在應(yīng)用程序代碼中處理和創(chuàng)建異常過濾器。

在本章中,您特別學(xué)習(xí)了如何處理由 Prisma 引發(fā)的錯(cuò)誤。但請(qǐng)注意,這些技術(shù)不僅適用于 Prisma,它們同樣可以用于處理應(yīng)用程序中的其他任何類型錯(cuò)誤。

您可以在 end-error-handling-part-3 分支上找到本教程的結(jié)束點(diǎn)。如果您在學(xué)習(xí)過程中遇到問題,請(qǐng)隨時(shí)在存儲(chǔ)庫中提出疑問或提交 PR(Pull Request)。當(dāng)然,您也可以直接在 Twitter 上與我聯(lián)系。

原文鏈接:https://www.prisma.io/blog/nestjs-prisma-error-handling-7D056s1kOop2

上一篇:

使用NestJS和Prisma構(gòu)建REST API:輸入驗(yàn)證和轉(zhuǎn)換

下一篇:

Photo StoryTelling —— 利用生成式AI與Google API,在您的相冊(cè)中進(jìn)行創(chuàng)作
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊(cè)

多API并行試用

數(shù)據(jù)驅(qū)動(dòng)選型,提升決策效率

查看全部API→
??

熱門場(chǎng)景實(shí)測(cè),選對(duì)API

#AI文本生成大模型API

對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)

#AI深度推理大模型API

對(duì)比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)