
如何快速實(shí)現(xiàn)REST API集成以優(yōu)化業(yè)務(wù)流程
getHello(): string {
return this.appService.getHello();
}
讓我們?cè)陧?xiàng)目文件夾中執(zhí)行?npm run start:dev
?命令。這將以監(jiān)視模式啟動(dòng)我們的 NestJS 應(yīng)用程序,并實(shí)現(xiàn)實(shí)時(shí)重新加載功能,即當(dāng)應(yīng)用程序文件發(fā)生更改時(shí),會(huì)自動(dòng)重新加載。
一旦 NestJS 運(yùn)行起來,我們?cè)?Web 瀏覽器中打開 http://localhost:3000
。這時(shí),應(yīng)該會(huì)看到一個(gè)顯示 “Hello World!” 問候語的空白頁面。
此外,我們還可以使用 API 測(cè)試工具(如 Insomnia)向 http://localhost:3000
發(fā)送 GET 請(qǐng)求,同樣會(huì)收到 “Hello World!” 的響應(yīng)。
接下來,我們將刪除這個(gè)端點(diǎn),因?yàn)樗皇?Nest CLI 添加的用于演示的示例。為此,需要?jiǎng)h除 app.controller.ts
、app.service.ts
和 app.controller.spec.ts
文件。同時(shí),還要在 AppController
和 app.module.ts
中刪除對(duì)這些文件的所有引用。
NestJS 的架構(gòu)設(shè)計(jì)鼓勵(lì)我們按照功能來組織模塊。這種基于功能的設(shè)計(jì)將單個(gè)功能相關(guān)的代碼分組到一個(gè)文件夾中,并在一個(gè)模塊中進(jìn)行注冊(cè)。這種設(shè)計(jì)方式簡(jiǎn)化了代碼庫(kù),使得代碼拆分變得更加容易。
在 NestJS 中,我們通過使用?@Module
?裝飾器來創(chuàng)建模塊。模塊用于注冊(cè)控制器、服務(wù)和任何其他需要導(dǎo)入的子模塊。導(dǎo)入的子模塊也可以注冊(cè)自己的控制器和服務(wù)。
現(xiàn)在,我們將使用 Nest CLI 為我們的博客文章創(chuàng)建一個(gè)模塊。
nest generate module posts
執(zhí)行上述命令后,PostsModule
文件夾中會(huì)生成一個(gè)空的 posts.module.ts
類文件。
接下來,我們將利用 TypeScript 的 interface
來明確博客文章 JSON 對(duì)象的結(jié)構(gòu)。
interface
?在 TypeScript 中被視為一種虛擬或抽象結(jié)構(gòu),它僅存在于 TypeScript 層面。interface
?主要用于 TypeScript 編譯器的類型檢查,它不會(huì)在 TypeScript 轉(zhuǎn)換為 JavaScript 的過程中生成任何 JavaScript 代碼。
現(xiàn)在,我們將借助 Nest CLI 來創(chuàng)建所需的 interface
。
cd src/posts
nest generate interface posts
這些命令會(huì)在功能相關(guān)的文件夾 /src/posts
中為我們的博客文章創(chuàng)建一個(gè) posts.interface.ts
文件。
在使用 TypeScript 的 interface
關(guān)鍵字來定義接口時(shí),請(qǐng)確保在接口聲明前加上 export
關(guān)鍵字,這樣就可以在整個(gè)應(yīng)用程序中引用和使用該接口了。
export interface PostModel {
id?: number;
date: Date;
title: string;
body: string;
category: string;
}
在命令行提示符下,讓我們使用以下命令將當(dāng)前工作目錄重置回項(xiàng)目的根文件夾。
cd ../..
Service 類是用于處理業(yè)務(wù)邏輯的部分。我們將要?jiǎng)?chuàng)建的?PostsService
?將專門負(fù)責(zé)處理與博客文章管理相關(guān)的業(yè)務(wù)邏輯。
接下來,我們使用 Nest CLI 來為我們的博客文章創(chuàng)建一個(gè)服務(wù)。
nest generate service posts
執(zhí)行上述命令后,PostsService
文件夾中會(huì)生成一個(gè)空的 posts.service.ts
類文件。
@Injectable()
裝飾器的作用是將 PostsService
類標(biāo)記為一個(gè)提供者(Provider),這樣我們就可以將其注冊(cè)到 PostsModule
的 providers
數(shù)組中,并隨后在控制器類中進(jìn)行注入。關(guān)于這部分的詳細(xì)內(nèi)容,稍后會(huì)進(jìn)行詳細(xì)介紹。
Controller 是負(fù)責(zé)處理傳入的請(qǐng)求并向客戶端返回響應(yīng)的類。一個(gè)控制器可以包含多個(gè)路由(或稱為端點(diǎn)),每個(gè)路由都可以實(shí)現(xiàn)一組特定的操作。NestJS 的路由機(jī)制會(huì)根據(jù)請(qǐng)求的 URL 將其路由到正確的控制器上。
現(xiàn)在,我們使用 Nest CLI 來為我們的博客文章創(chuàng)建一個(gè)控制器。
nest generate controller posts
執(zhí)行上述命令后,PostsService
相關(guān)的文件夾中會(huì)生成一個(gè)空的 posts.service.ts
類文件。
接下來,我們需要將 PostsService
注入到 PostsController
類的構(gòu)造函數(shù)中。
import { Controller } from '@nestjs/common';
import { PostsService } from './posts.service';
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
}
NestJS 利用依賴注入機(jī)制,在控制器中自動(dòng)設(shè)置對(duì)?PostsService
?的引用。這樣,我們就可以在控制器類中方便地通過?this.postsService
?來調(diào)用其提供的方法了。
我們需要確保 PostsModule
已經(jīng)正確注冊(cè)了 PostsController
和 PostsService
。值得慶幸的是,之前執(zhí)行的 nest generate service post
和 nest generate controller post
命令已經(jīng)自動(dòng)在 PostsModule
中完成了 PostsService
和 PostsController
類的注冊(cè)工作。
@Module({
controllers: [PostsController],
providers: [PostsService],
})
export class PostsModule {}
在 NestJS 中,“providers” 這一術(shù)語被用來指代 service classes(服務(wù)類)、middleware(中間件)、guards(守衛(wèi))等。
如果希望 PostsService
類能夠被我們應(yīng)用程序中的其他模塊所使用,我們可以在 PostsModule
的 exports
數(shù)組中將其導(dǎo)出。這樣一來,任何導(dǎo)入了 PostsModule
的模塊都能夠訪問并使用 PostsService
。
@Module({
controllers: [PostsController],
providers: [PostsService],
exports: [PostsService],
})
export class PostsModule {}
現(xiàn)在,我們已經(jīng)擁有了一個(gè)內(nèi)聚且組織良好的模塊,它涵蓋了與博客文章相關(guān)的所有功能。但請(qǐng)注意,除非?AppModule
?導(dǎo)入了?PostsModule
,否則?PostsModule
?中的功能是無法在應(yīng)用程序中使用的。
如果我們?cè)俅尾榭?nbsp;AppModule
,會(huì)發(fā)現(xiàn) PostsModule
已經(jīng)通過之前執(zhí)行的 nest generate module post
命令自動(dòng)添加到了 imports
數(shù)組中。這意味著 AppModule
已經(jīng)成功導(dǎo)入了 PostsModule
,從而使得 PostsModule
中的功能可以在整個(gè)應(yīng)用程序中使用。
@Module({
imports: [PostsModule],
controllers: [],
providers: [],
})
export class AppModule {}
目前,我們的 PostsService
和 PostsController
都尚未實(shí)現(xiàn)任何功能。現(xiàn)在,我們將遵循 RESTful 標(biāo)準(zhǔn)來實(shí)現(xiàn) CRUD(創(chuàng)建、讀取、更新、刪除)端點(diǎn)及其對(duì)應(yīng)的邏輯。
NestJS 提供了與 MongoDB(通過?@nestjs/mongoose
?包)或 PostgreSQL(通過 Prisma 或 TypeORM)等數(shù)據(jù)庫(kù)集成和持久化應(yīng)用數(shù)據(jù)的便捷方式。但為了演示的簡(jiǎn)潔性,我們將在?PostsService
?中使用一個(gè)本地?cái)?shù)組來模擬數(shù)據(jù)庫(kù)的功能。
import { Injectable } from '@nestjs/common';
import { PostModel } from './posts.interface';
@Injectable()
export class PostsService {
private posts: Array<PostModel> = [];
}
在 NestJS 中,服務(wù)(或提供者)的作用域是可以配置的。默認(rèn)情況下,服務(wù)是單例的,這意味著在整個(gè)應(yīng)用程序中只會(huì)存在一個(gè)服務(wù)的實(shí)例,并且該實(shí)例會(huì)被共享。服務(wù)的初始化僅在應(yīng)用程序啟動(dòng)時(shí)進(jìn)行一次。由于服務(wù)的默認(rèn)作用域是單例,因此任何注入 PostsService
的類都將訪問到內(nèi)存中相同的 posts
數(shù)組數(shù)據(jù)。
現(xiàn)在,我們來在 PostsService
中添加一個(gè)方法,用于返回所有的博客文章。
public findAll(): Array<PostModel> {
return this.posts;
}
接下來,我們將在?PostsController
?中添加一個(gè)方法,以便將?PostsService
?的?findAll()
?方法的邏輯暴露給客戶端請(qǐng)求。
@Get()
public findAll(): Array<PostModel> {
return this.postsService.findAll();
}
@Get
裝飾器被用于創(chuàng)建一個(gè) GET 請(qǐng)求的 /posts
端點(diǎn)。這個(gè)端點(diǎn)的路徑 /posts
是由定義在控制器上的 @Controller('posts')
裝飾器提供的。
讓我們?yōu)?code>PostsService添加一個(gè)方法,該方法能夠返回客戶端可能想要查找的特定博客文章。如果在我們維護(hù)的帖子列表中未能找到與請(qǐng)求中指定的帖子 ID 相匹配的條目,我們將返回一個(gè) 404 NOT FOUND HTTP 錯(cuò)誤,以表明所請(qǐng)求的資源未找到。
public findOne(id: number): PostModel {
const post: PostModel = this.posts.find(post => post.id === id);
if (!post) {
throw new NotFoundException('Post not found.');
}
return post;
}
讓我們?cè)?code>PostsController中添加一個(gè)方法,它將使服務(wù)的findAll()
方法的邏輯可用于客戶端請(qǐng)求。
@Get(':id')
public findOne(@Param('id', ParseIntPipe) id: number): PostModel {
return this.postsService.findOne(id);
}
在這里,@Get
裝飾器與參數(shù)裝飾器 @Param('id')
一起使用,用于創(chuàng)建 GET /post/:id
端點(diǎn),其中 :id
是代表博客文章唯一標(biāo)識(shí)的動(dòng)態(tài)路由參數(shù)。
@Param
裝飾器來自 @nestjs/common
包,它能夠?qū)⒙酚蓞?shù)作為方法參數(shù)直接提供給我們使用。需要注意的是,@Param
裝飾器獲取到的值默認(rèn)是字符串類型。由于我們?cè)?TypeScript 中將 id
定義為數(shù)字類型,因此需要進(jìn)行字符串到數(shù)字的轉(zhuǎn)換。NestJS 提供了多種管道(Pipe),允許我們對(duì)請(qǐng)求參數(shù)進(jìn)行轉(zhuǎn)換和驗(yàn)證。在這里,我們可以使用 NestJS 的 ParseIntPipe
來將 id
字符串轉(zhuǎn)換為數(shù)字類型。
讓我們?cè)?code>PostsService中添加一個(gè)方法,用于創(chuàng)建一個(gè)新的博客文章。這個(gè)方法需要為新文章分配一個(gè)順序遞增的?id
,并返回創(chuàng)建后的文章對(duì)象。另外,如果新文章的標(biāo)題(title
)已經(jīng)與現(xiàn)有文章重復(fù),我們將拋出一個(gè) 422 UNPROCESSABLE ENTITY HTTP 錯(cuò)誤,表示請(qǐng)求實(shí)體無法處理。
public create(post: PostModel): PostModel {
// if the title is already in use by another post
const titleExists: boolean = this.posts.some(
(item) => item.title === post.title,
);
if (titleExists) {
throw new UnprocessableEntityException('Post title already exists.');
}
// find the next id for a new blog post
const maxId: number = Math.max(...this.posts.map((post) => post.id), 0);
const id: number = maxId + 1;
const blogPost: PostModel = {
...post,
id,
};
this.posts.push(blogPost);
return blogPost;
}
讓我們?cè)?code>PostsController中添加一個(gè)方法,它將使服務(wù)的findAll()
方法的邏輯可用于客戶端請(qǐng)求。
@Post()
public create(@Body() post: PostModel): PostModel {
return this.postsService.create(post);
}
@Post
裝飾器被用來創(chuàng)建一個(gè) POST /post
端點(diǎn)。
在 NestJS 中,當(dāng)我們使用?POST
、PUT
?和?PATCH
?等 HTTP 方法裝飾器時(shí),HTTP 請(qǐng)求的主體(Body)通常用于向 API 傳輸數(shù)據(jù),這些數(shù)據(jù)一般采用 JSON 格式。
為了解析 HTTP 請(qǐng)求的主體,我們可以使用 @Body
裝飾器。當(dāng)使用這個(gè)裝飾器時(shí),NestJS 會(huì)自動(dòng)對(duì) HTTP 請(qǐng)求的主體執(zhí)行 JSON.parse()
操作,并將解析后的 JSON 對(duì)象作為參數(shù)傳遞給控制器的方法。在這個(gè)場(chǎng)景中,我們期望客戶端發(fā)送的數(shù)據(jù)符合 Post
類型的結(jié)構(gòu),因此在 @Body
裝飾器中,我們將參數(shù)類型聲明為 Post
。
讓我們?cè)?code>PostsService中添加一個(gè)方法,該方法使用 JavaScript 的?splice()
?方法從內(nèi)存中的帖子數(shù)組中移除指定的博客帖子。如果在我們維護(hù)的帖子列表中找不到與請(qǐng)求中指定的帖子 ID 相匹配的條目,我們將返回一個(gè) 404 NOT FOUND HTTP 錯(cuò)誤,表明所請(qǐng)求的資源未找到。
public delete(id: number): void {
const index: number = this.posts.findIndex(post => post.id === id);
// -1 is returned when no findIndex() match is found
if (index === -1) {
throw new NotFoundException('Post not found.');
}
this.posts.splice(index, 1);
}
讓我們?cè)?code>PostsController中添加一個(gè)方法,它將使服務(wù)的findAll()
方法的邏輯可用于客戶端請(qǐng)求。
@Delete(':id')
public delete(@Param('id', ParseIntPipe) id: number): void {
this.postsService.delete(id);
}
讓我們?yōu)?code>PostsService添加一個(gè)方法,用于查找具有指定?id
?的博客文章,并使用新提交的數(shù)據(jù)對(duì)其進(jìn)行更新。更新完成后,該方法將返回更新后的文章對(duì)象。如果在我們的帖子列表中未找到與請(qǐng)求中指定的?id
?相匹配的條目,我們將返回一個(gè) 404 NOT FOUND HTTP 錯(cuò)誤,表明所請(qǐng)求的資源未找到。另外,如果新提交的標(biāo)題(title
)已經(jīng)被其他博客文章使用,我們將拋出一個(gè) 422 UNPROCESSABLE ENTITY HTTP 錯(cuò)誤,表示請(qǐng)求實(shí)體無法處理。
public update(id: number, post: PostModel): PostModel {
this.logger.log(Updating post with id: ${id}
);
const index: number = this.posts.findIndex((post) => post.id === id);
// -1 is returned when no findIndex() match is found
if (index === -1) {
throw new NotFoundException('Post not found.');
}
// if the title is already in use by another post
const titleExists: boolean = this.posts.some(
(item) => item.title === post.title && item.id !== id,
);
if (titleExists) {
throw new UnprocessableEntityException('Post title already exists.');
}
const blogPost: PostModel = {
...post,
id,
};
this.posts[index] = blogPost;
return blogPost;
}
讓我們?cè)?code>PostsController中添加一個(gè)方法,它將使服務(wù)的findAll()
方法的邏輯可用于客戶端請(qǐng)求。
@Put(':id')
public update(@Param('id', ParseIntPipe) id: number, @Body() post: PostModel): PostModel {
return this.postsService.update(id, post);
}
我們使用 @Put
裝飾器來處理 HTTP PUT 請(qǐng)求方法。PUT 方法既可以用于創(chuàng)建新資源,也可以用于更新服務(wù)器上已有資源的狀態(tài)。當(dāng)服務(wù)器上的資源已存在且我們知道其位置時(shí),PUT 請(qǐng)求將替換該資源的當(dāng)前狀態(tài)。
首先,使用 npm run start:dev
命令啟動(dòng)我們的開發(fā)服務(wù)器。然后,打開 Incubator 應(yīng)用程序,以便測(cè)試我們?yōu)?nbsp;PostsModule
創(chuàng)建的 API 端點(diǎn)。
向?http://localhost:3000/posts
?發(fā)送 GET 請(qǐng)求。預(yù)期結(jié)果是一個(gè)表示空數(shù)組的響應(yīng),并附帶 200 OK 成功狀態(tài)碼。
接下來,向 http://localhost:3000/posts
發(fā)送 POST 請(qǐng)求,并在請(qǐng)求體中包含以下 JSON 數(shù)據(jù):
{
"date": "2021-08-16",
"title": "Intro to NestJS",
"body": "This blog post is about NestJS",
"category": "NestJS"
}
我們應(yīng)該會(huì)收到一個(gè) 201 Created 響應(yīng)代碼,表示帖子已成功創(chuàng)建。同時(shí),響應(yīng)體中還會(huì)包含一個(gè) JSON 對(duì)象,該對(duì)象表示已創(chuàng)建的帖子,并包括一個(gè)自動(dòng)生成的?id
?字段。
接下來,向? http://localhost:3000/posts/1
?發(fā)送 GET 請(qǐng)求。預(yù)期結(jié)果是一個(gè) 200 OK 響應(yīng)代碼,以及包含帖子數(shù)據(jù)的響應(yīng)體,其中?id
?字段的值為 1。
讓我們使用下面的 JSON 數(shù)據(jù)體向 http://localhost:3000/posts
發(fā)送一個(gè) POST 請(qǐng)求。
{
"date": "2021-08-16",
"title": "Intro to TypeScript",
"body": "An intro to TypeScript",
"category": "TypeScript"
}
結(jié)果應(yīng)該是一個(gè) 200 OK 響應(yīng)代碼,同時(shí)響應(yīng)體中會(huì)包含一個(gè) JSON 對(duì)象,該對(duì)象表示已更新后的帖子。
接下來,向 http://localhost:3000/posts/1
發(fā)送 DELETE 請(qǐng)求。預(yù)期結(jié)果是一個(gè) 200 OK 響應(yīng)代碼,并且響應(yīng)體中不包含任何 JSON 對(duì)象。
NestJS 使得在應(yīng)用程序中添加日志記錄變得非常簡(jiǎn)單。我們應(yīng)該使用 NestJS 提供的日志記錄功能,而不是直接使用 console.log()
語句或原生的 Logger
類。NestJS 的日志記錄功能會(huì)在終端中為我們提供格式良好、易于閱讀的日志消息。
要在我們的 API 中添加日志記錄,第一步是在服務(wù)類中定義一個(gè) logger 實(shí)例。
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { PostModel } from './posts.interface';
@Injectable()
export class PostsService {
private posts: Array<PostModel> = [];
private readonly logger = new Logger(PostsService.name);
// ...
}
現(xiàn)在我們已經(jīng)定義了日志記錄器,接下來可以在服務(wù)類中添加日志語句。以下是一個(gè)日志語句的示例,我們可以將它作為 findAll()
方法的第一行代碼添加到 PostsService
類中。
this.logger.log('Returning all posts');
在客戶端向我們的 API 發(fā)起請(qǐng)求時(shí),每次調(diào)用服務(wù)方法,這類日志語句都會(huì)在終端上輸出相應(yīng)的日志消息,為我們提供便利。
當(dāng)發(fā)送GET /posts
請(qǐng)求時(shí),我們應(yīng)該在終端中看到以下消息。
[PostsService] Returning all posts.
NestJS 使得利用 NestJS Swagger 包將 OpenAPI 規(guī)范集成到我們的 API 中變得輕而易舉。OpenAPI 規(guī)范是一種用于描述 RESTful API 的標(biāo)準(zhǔn),它主要用于文檔化和提供參考信息。
讓我們?yōu)?NestJS 安裝 Swagger。
npm install --save @nestjs/swagger swagger-ui-express
讓我們通過添加Swagger配置來更新引導(dǎo)NestJS應(yīng)用程序的main.ts
文件。
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('Blog API')
.setDescription('Blog API')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
await app.listen(3000);
}
bootstrap();
當(dāng) NestJS 應(yīng)用程序正在運(yùn)行時(shí),我們現(xiàn)在可以訪問 http://localhost:3000/api
來查看 API 的 Swagger 文檔。請(qǐng)注意,默認(rèn)情況下,“default”會(huì)顯示在我們帖子相關(guān)路由的上方作為標(biāo)簽。
為了改變這一點(diǎn),我們可以在 PostsController
類中的 @Controller('posts')
裝飾器下方添加 @ApiTags('posts')
裝飾器。這將用 “posts” 替換 “default”,以清晰地表明這組端點(diǎn)屬于 “posts” 功能或特性集。
@Controller('posts')
@ApiTags('posts')
為了使 PostModel
接口中的屬性對(duì) Swagger 可見,我們需要使用 @ApiProperty()
裝飾器(對(duì)于必填字段)或 @ApiPropertyOptional()
裝飾器(對(duì)于可選字段)來注釋這些字段。但請(qǐng)注意,由于裝飾器通常用于類屬性,我們需要將接口更改為類,以便能夠應(yīng)用這些裝飾器。
因此,我們的下一步是將 posts.interface.ts
文件中的 PostModel
接口轉(zhuǎn)換為類,并在相應(yīng)的屬性上使用 @ApiProperty()
或 @ApiPropertyOptional()
裝飾器。
export class PostModel {
@ApiPropertyOptional({ type: Number })
id?: number;
@ApiProperty({ type: String, format: 'date-time' })
date: Date;
@ApiProperty({ type: String })
title: string;
@ApiProperty({ type: String })
body: string;
@ApiProperty({ type: String })
category: string;
}
在?@ApiProperty()
?裝飾器中,我們?yōu)槊總€(gè)字段指定了類型。值得注意的是,id
?字段被標(biāo)記為可選,因?yàn)樵趧?chuàng)建新的博客文章時(shí),我們通常不知道它的?id
?會(huì)是什么(通常由數(shù)據(jù)庫(kù)自動(dòng)生成)。同時(shí),date
?字段被指定為使用?date-time
?字符串格式。
這些更改使得 PostModel
的結(jié)構(gòu)能夠在 Swagger 中得到正確的記錄。當(dāng) NestJS 應(yīng)用程序運(yùn)行時(shí),我們可以訪問 http://localhost:3000/api
來查看 PostModel
的詳細(xì)文檔。
接下來,我們將利用 Swagger 的?@ApiResponse()
?裝飾器來全面記錄 API 端點(diǎn)可能返回的所有響應(yīng)類型。這樣做有助于我們的 API 用戶清晰地了解,通過調(diào)用特定的端點(diǎn),他們可以獲得哪些類型的響應(yīng)。我們將在?PostsController
?類中實(shí)施這些更改。
對(duì)于 findAll
方法,我們將使用 @ApiOkResponse()
裝飾器來明確記錄 200 OK 成功響應(yīng)的詳細(xì)信息。
@Get()
@ApiOkResponse({ description: 'Posts retrieved successfully.'})
public findAll(): Array<PostModel> {
return this.postsService.findAll();
}
對(duì)于 findOne
方法,當(dāng)成功找到對(duì)應(yīng)的帖子時(shí),我們使用 @ApiOkResponse()
裝飾器來記錄 200 OK 響應(yīng)。而當(dāng)未找到帖子時(shí),我們則使用 @ApiNotFoundResponse()
裝飾器來記錄 404 NOT FOUND HTTP 錯(cuò)誤。
@Get(':id')
@ApiOkResponse({ description: 'Post retrieved successfully.'})
@ApiNotFoundResponse({ description: 'Post not found.' })
public findOne(@Param('id', ParseIntPipe) id: number): PostModel {
return this.postsService.findOne(id);
}
對(duì)于?create
?方法,當(dāng)成功創(chuàng)建一個(gè)新的帖子時(shí),我們將使用?@ApiCreatedResponse()
?裝飾器來記錄 201 CREATED 響應(yīng)。而當(dāng)檢測(cè)到重復(fù)的文章標(biāo)題時(shí),我們會(huì)使用?@ApiUnprocessableEntityResponse()
?裝飾器來記錄一個(gè) 422 UNPROCESSABLE ENTITY HTTP 錯(cuò)誤。
@Post()
@ApiCreatedResponse({ description: 'Post created successfully.' })
@ApiUnprocessableEntityResponse({ description: 'Post title already exists.' })
public create(@Body() post: PostModel): void {
return this.postsService.create(post);
}
對(duì)于?delete
?方法,如果帖子被成功刪除,我們將使用?@ApiOkResponse()
?裝飾器來記錄 200 OK 響應(yīng)。而當(dāng)嘗試刪除一個(gè)不存在的帖子時(shí),我們會(huì)使用?@ApiNotFoundResponse()
?裝飾器來記錄一個(gè) 404 NOT FOUND HTTP 錯(cuò)誤。
@Delete(':id')
@ApiOkResponse({ description: 'Post deleted successfully.'})
@ApiNotFoundResponse({ description: 'Post not found.' })
public delete(@Param('id', ParseIntPipe) id: number): void {
return this.postsService.delete(id);
}
對(duì)于 update
方法,當(dāng)帖子被成功更新時(shí),我們將使用 @ApiOkResponse()
裝飾器來記錄 200 OK 響應(yīng)。如果嘗試更新一個(gè)不存在的帖子,我們會(huì)使用 @ApiNotFoundResponse()
裝飾器來記錄一個(gè) 404 NOT FOUND HTTP 錯(cuò)誤。另外,當(dāng)發(fā)現(xiàn)存在重復(fù)的帖子標(biāo)題時(shí),我們會(huì)使用 @ApiUnprocessableEntityResponse()
裝飾器來記錄一個(gè) 422 UNPROCESSABLE ENTITY HTTP 錯(cuò)誤。
@Put(':id')
@ApiOkResponse({ description: 'Post updated successfully.'})
@ApiNotFoundResponse({ description: 'Post not found.' })
@ApiUnprocessableEntityResponse({ description: 'Post title already exists.' })
public update(@Param('id', ParseIntPipe) id: number, @Body() post: PostModel): void {
return this.postsService.update(id, post);
}
保存上述更改后,現(xiàn)在您應(yīng)該能夠在 Swagger 網(wǎng)頁的 http://localhost:3000/api
地址上查看到每個(gè)端點(diǎn)的所有響應(yīng)代碼及其相應(yīng)的描述信息。
此外,我們可以利用 Swagger 來測(cè)試我們的 API,而不僅僅是依賴 Inclusive。只需在 Swagger 網(wǎng)頁上點(diǎn)擊每個(gè)端點(diǎn)下方的“Try it out”按鈕,即可輕松驗(yàn)證這些端點(diǎn)是否按預(yù)期正常工作。
異常過濾器為我們提供了對(duì) NestJS 異常處理層的全面掌控。通過它,我們可以為 HTTP 異常響應(yīng)主體添加自定義字段,或者記錄終端上發(fā)生的每個(gè) HTTP 異常的日志信息。
接下來,我們需要在 /src/filters
文件夾中創(chuàng)建一個(gè)新的文件,命名為 http-exception.filter.ts
。然后,在這個(gè)文件中,我們將定義一個(gè)異常過濾器類。
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, Logger } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(HttpExceptionFilter.name);
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const statusCode = exception.getStatus();
const message = exception.message || null;
const body = {
statusCode,
message,
timestamp: new Date().toISOString(),
endpoint: request.url,
};
this.logger.warn(${statusCode} ${message}
);
response
.status(statusCode)
.json(body);
}
}
這個(gè)類會(huì)利用 NestJS 的記錄器功能,在 HTTP 異常產(chǎn)生時(shí)向終端輸出警告信息。同時(shí),當(dāng) HTTP 異常發(fā)生時(shí),它還會(huì)在響應(yīng)體中包含兩個(gè)自定義字段:timestamp
?字段用于記錄異常發(fā)生的時(shí)間點(diǎn),而?endpoint
?字段則用于指明是哪個(gè)路由觸發(fā)了該異常。
為了將這個(gè)過濾器應(yīng)用到 PostsController
上,我們需要使用 @UseFilters(HttpExceptionFilter)
裝飾器,并傳入 HttpExceptionFilter
類的一個(gè)新實(shí)例。
@Controller('posts')
@UseFilters(new HttpExceptionFilter())
export class PostsController {
constructor(private readonly postsService: PostsService) {}
}
保存這些更改后,NestJS將重新加載我們的應(yīng)用程序。如果我們使用Inclusion向我們的API發(fā)送一個(gè)PUT /posts/1
請(qǐng)求,它應(yīng)該會(huì)觸發(fā)一個(gè)404 NOT FOUND
HTTP錯(cuò)誤,因?yàn)楫?dāng)它啟動(dòng)時(shí),我們的應(yīng)用程序中不存在可供更新的博客文章。返回到Incubator的HTTP異常響應(yīng)主體現(xiàn)在應(yīng)該包含timestamp
和endpoint
字段。
{
"statusCode": 404,
"message": "Post not found.",
"timestamp": "2021-08-23T21:05:29.497Z",
"endpoint": "/posts/1"
}
我們還應(yīng)該看到下面這行打印到終端。
WARN [HttpExceptionFilter] 404 Post not found.
在本文中,我們深入了解了 NestJS 如何讓后端 API 開發(fā)變得迅速、簡(jiǎn)潔且高效。NestJS 提供的應(yīng)用程序結(jié)構(gòu)助力我們構(gòu)建出結(jié)構(gòu)清晰、組織有序的項(xiàng)目。
我們涵蓋了很多內(nèi)容,所以讓我們回顧一下我們學(xué)到的內(nèi)容:
希望你在使用 NestJS 進(jìn)行開發(fā)時(shí)能夠感受到它的強(qiáng)大與便捷,享受開發(fā)的樂趣!
原文鏈接:https://www.thisdot.co/blog/introduction-to-restful-apis-with-nestjs
對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力
一鍵對(duì)比試用API 限時(shí)免費(fèi)