getHello(): string {
return this.appService.getHello();
}

讓我們在項目文件夾中執行?npm run start:dev?命令。這將以監視模式啟動我們的 NestJS 應用程序,并實現實時重新加載功能,即當應用程序文件發生更改時,會自動重新加載。

一旦 NestJS 運行起來,我們在 Web 瀏覽器中打開 http://localhost:3000。這時,應該會看到一個顯示 “Hello World!” 問候語的空白頁面。

此外,我們還可以使用 API 測試工具(如 Insomnia)向 http://localhost:3000 發送 GET 請求,同樣會收到 “Hello World!” 的響應。

接下來,我們將刪除這個端點,因為它只是 Nest CLI 添加的用于演示的示例。為此,需要刪除 app.controller.tsapp.service.ts 和 app.controller.spec.ts 文件。同時,還要在 AppController 和 app.module.ts 中刪除對這些文件的所有引用。

創建特性模塊

NestJS 的架構設計鼓勵我們按照功能來組織模塊。這種基于功能的設計將單個功能相關的代碼分組到一個文件夾中,并在一個模塊中進行注冊。這種設計方式簡化了代碼庫,使得代碼拆分變得更加容易。

模塊

在 NestJS 中,我們通過使用?@Module?裝飾器來創建模塊。模塊用于注冊控制器、服務和任何其他需要導入的子模塊。導入的子模塊也可以注冊自己的控制器和服務。

現在,我們將使用 Nest CLI 為我們的博客文章創建一個模塊。

nest generate module posts

執行上述命令后,PostsModule 文件夾中會生成一個空的 posts.module.ts 類文件。

定義接口

接下來,我們將利用 TypeScript 的 interface 來明確博客文章 JSON 對象的結構。

interface?在 TypeScript 中被視為一種虛擬或抽象結構,它僅存在于 TypeScript 層面。interface?主要用于 TypeScript 編譯器的類型檢查,它不會在 TypeScript 轉換為 JavaScript 的過程中生成任何 JavaScript 代碼。

現在,我們將借助 Nest CLI 來創建所需的 interface

cd src/posts 
nest generate interface posts

這些命令會在功能相關的文件夾 /src/posts 中為我們的博客文章創建一個 posts.interface.ts 文件。

在使用 TypeScript 的 interface 關鍵字來定義接口時,請確保在接口聲明前加上 export 關鍵字,這樣就可以在整個應用程序中引用和使用該接口了。

export interface PostModel {
id?: number;
date: Date;
title: string;
body: string;
category: string;
}

在命令行提示符下,讓我們使用以下命令將當前工作目錄重置回項目的根文件夾。

cd ../..

Service

Service 類是用于處理業務邏輯的部分。我們將要創建的?PostsService?將專門負責處理與博客文章管理相關的業務邏輯。

接下來,我們使用 Nest CLI 來為我們的博客文章創建一個服務。

nest generate service posts

執行上述命令后,PostsService 文件夾中會生成一個空的 posts.service.ts 類文件。

@Injectable() 裝飾器的作用是將 PostsService 類標記為一個提供者(Provider),這樣我們就可以將其注冊到 PostsModule 的 providers 數組中,并隨后在控制器類中進行注入。關于這部分的詳細內容,稍后會進行詳細介紹。

Controller

Controller 是負責處理傳入的請求并向客戶端返回響應的類。一個控制器可以包含多個路由(或稱為端點),每個路由都可以實現一組特定的操作。NestJS 的路由機制會根據請求的 URL 將其路由到正確的控制器上。

現在,我們使用 Nest CLI 來為我們的博客文章創建一個控制器。

nest generate controller posts

執行上述命令后,PostsService 相關的文件夾中會生成一個空的 posts.service.ts 類文件。

接下來,我們需要將 PostsService 注入到 PostsController 類的構造函數中。

import { Controller } from '@nestjs/common';
import { PostsService } from './posts.service';

@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
}

NestJS 利用依賴注入機制,在控制器中自動設置對?PostsService?的引用。這樣,我們就可以在控制器類中方便地通過?this.postsService?來調用其提供的方法了。

關于控制器和服務的注冊:

我們需要確保 PostsModule 已經正確注冊了 PostsController 和 PostsService。值得慶幸的是,之前執行的 nest generate service post 和 nest generate controller post 命令已經自動在 PostsModule 中完成了 PostsService 和 PostsController 類的注冊工作。

@Module({
controllers: [PostsController],
providers: [PostsService],
})
export class PostsModule {}

在 NestJS 中,“providers” 這一術語被用來指代 service classes(服務類)、middleware(中間件)、guards(守衛)等。

如果希望 PostsService 類能夠被我們應用程序中的其他模塊所使用,我們可以在 PostsModule 的 exports 數組中將其導出。這樣一來,任何導入了 PostsModule 的模塊都能夠訪問并使用 PostsService

@Module({
controllers: [PostsController],
providers: [PostsService],
exports: [PostsService],
})
export class PostsModule {}

將 PostsModule 導入到 AppModule 中

現在,我們已經擁有了一個內聚且組織良好的模塊,它涵蓋了與博客文章相關的所有功能。但請注意,除非?AppModule?導入了?PostsModule,否則?PostsModule?中的功能是無法在應用程序中使用的。

如果我們再次查看 AppModule,會發現 PostsModule 已經通過之前執行的 nest generate module post 命令自動添加到了 imports 數組中。這意味著 AppModule 已經成功導入了 PostsModule,從而使得 PostsModule 中的功能可以在整個應用程序中使用。

@Module({
imports: [PostsModule],
controllers: [],
providers: [],
})
export class AppModule {}

添加 Service 和 Controller 邏輯

目前,我們的 PostsService 和 PostsController 都尚未實現任何功能。現在,我們將遵循 RESTful 標準來實現 CRUD(創建、讀取、更新、刪除)端點及其對應的邏輯。

NestJS 提供了與 MongoDB(通過?@nestjs/mongoose?包)或 PostgreSQL(通過 Prisma 或 TypeORM)等數據庫集成和持久化應用數據的便捷方式。但為了演示的簡潔性,我們將在?PostsService?中使用一個本地數組來模擬數據庫的功能。

import { Injectable } from '@nestjs/common';
import { PostModel } from './posts.interface';

@Injectable()
export class PostsService {
private posts: Array<PostModel> = [];
}

在 NestJS 中,服務(或提供者)的作用域是可以配置的。默認情況下,服務是單例的,這意味著在整個應用程序中只會存在一個服務的實例,并且該實例會被共享。服務的初始化僅在應用程序啟動時進行一次。由于服務的默認作用域是單例,因此任何注入 PostsService 的類都將訪問到內存中相同的 posts 數組數據。

獲取所有帖子

現在,我們來在 PostsService 中添加一個方法,用于返回所有的博客文章。

public findAll(): Array<PostModel> {
return this.posts;
}

接下來,我們將在?PostsController?中添加一個方法,以便將?PostsService?的?findAll()?方法的邏輯暴露給客戶端請求。

@Get()
public findAll(): Array<PostModel> {
return this.postsService.findAll();
}

@Get 裝飾器被用于創建一個 GET 請求的 /posts 端點。這個端點的路徑 /posts 是由定義在控制器上的 @Controller('posts') 裝飾器提供的。

獲取 1 篇博文

讓我們為PostsService添加一個方法,該方法能夠返回客戶端可能想要查找的特定博客文章。如果在我們維護的帖子列表中未能找到與請求中指定的帖子 ID 相匹配的條目,我們將返回一個 404 NOT FOUND HTTP 錯誤,以表明所請求的資源未找到。

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;
}

讓我們在PostsController中添加一個方法,它將使服務的findAll()方法的邏輯可用于客戶端請求。

@Get(':id')
public findOne(@Param('id', ParseIntPipe) id: number): PostModel {
return this.postsService.findOne(id);
}

在這里,@Get 裝飾器與參數裝飾器 @Param('id') 一起使用,用于創建 GET /post/:id 端點,其中 :id 是代表博客文章唯一標識的動態路由參數。

@Param 裝飾器來自 @nestjs/common 包,它能夠將路由參數作為方法參數直接提供給我們使用。需要注意的是,@Param 裝飾器獲取到的值默認是字符串類型。由于我們在 TypeScript 中將 id 定義為數字類型,因此需要進行字符串到數字的轉換。NestJS 提供了多種管道(Pipe),允許我們對請求參數進行轉換和驗證。在這里,我們可以使用 NestJS 的 ParseIntPipe 來將 id 字符串轉換為數字類型。

創建帖子

讓我們在PostsService中添加一個方法,用于創建一個新的博客文章。這個方法需要為新文章分配一個順序遞增的?id,并返回創建后的文章對象。另外,如果新文章的標題(title)已經與現有文章重復,我們將拋出一個 422 UNPROCESSABLE ENTITY HTTP 錯誤,表示請求實體無法處理。

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;
}

讓我們在PostsController中添加一個方法,它將使服務的findAll()方法的邏輯可用于客戶端請求。

@Post()
public create(@Body() post: PostModel): PostModel {
return this.postsService.create(post);
}

@Post 裝飾器被用來創建一個 POST /post 端點。

在 NestJS 中,當我們使用?POSTPUT?和?PATCH?等 HTTP 方法裝飾器時,HTTP 請求的主體(Body)通常用于向 API 傳輸數據,這些數據一般采用 JSON 格式。

為了解析 HTTP 請求的主體,我們可以使用 @Body 裝飾器。當使用這個裝飾器時,NestJS 會自動對 HTTP 請求的主體執行 JSON.parse() 操作,并將解析后的 JSON 對象作為參數傳遞給控制器的方法。在這個場景中,我們期望客戶端發送的數據符合 Post 類型的結構,因此在 @Body 裝飾器中,我們將參數類型聲明為 Post

刪除帖子

讓我們在PostsService中添加一個方法,該方法使用 JavaScript 的?splice()?方法從內存中的帖子數組中移除指定的博客帖子。如果在我們維護的帖子列表中找不到與請求中指定的帖子 ID 相匹配的條目,我們將返回一個 404 NOT FOUND HTTP 錯誤,表明所請求的資源未找到。

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);
}

讓我們在PostsController中添加一個方法,它將使服務的findAll()方法的邏輯可用于客戶端請求。

@Delete(':id')
public delete(@Param('id', ParseIntPipe) id: number): void {
this.postsService.delete(id);
}

更新帖子

讓我們為PostsService添加一個方法,用于查找具有指定?id?的博客文章,并使用新提交的數據對其進行更新。更新完成后,該方法將返回更新后的文章對象。如果在我們的帖子列表中未找到與請求中指定的?id?相匹配的條目,我們將返回一個 404 NOT FOUND HTTP 錯誤,表明所請求的資源未找到。另外,如果新提交的標題(title)已經被其他博客文章使用,我們將拋出一個 422 UNPROCESSABLE ENTITY HTTP 錯誤,表示請求實體無法處理。

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; }

讓我們在PostsController中添加一個方法,它將使服務的findAll()方法的邏輯可用于客戶端請求。

@Put(':id')
public update(@Param('id', ParseIntPipe) id: number, @Body() post: PostModel): PostModel {
return this.postsService.update(id, post);
}

我們使用 @Put 裝飾器來處理 HTTP PUT 請求方法。PUT 方法既可以用于創建新資源,也可以用于更新服務器上已有資源的狀態。當服務器上的資源已存在且我們知道其位置時,PUT 請求將替換該資源的當前狀態。

測試我們的功能模塊

首先,使用 npm run start:dev 命令啟動我們的開發服務器。然后,打開 Incubator 應用程序,以便測試我們為 PostsModule 創建的 API 端點。

獲取所有帖子

向?http://localhost:3000/posts?發送 GET 請求。預期結果是一個表示空數組的響應,并附帶 200 OK 成功狀態碼。

創建帖子

接下來,向 http://localhost:3000/posts 發送 POST 請求,并在請求體中包含以下 JSON 數據:

{
"date": "2021-08-16",
"title": "Intro to NestJS",
"body": "This blog post is about NestJS",
"category": "NestJS"
}

我們應該會收到一個 201 Created 響應代碼,表示帖子已成功創建。同時,響應體中還會包含一個 JSON 對象,該對象表示已創建的帖子,并包括一個自動生成的?id?字段。

獲取帖子

接下來,向? http://localhost:3000/posts/1?發送 GET 請求。預期結果是一個 200 OK 響應代碼,以及包含帖子數據的響應體,其中?id?字段的值為 1。

更新帖子

讓我們使用下面的 JSON 數據體向 http://localhost:3000/posts 發送一個 POST 請求。

{
"date": "2021-08-16",
"title": "Intro to TypeScript",
"body": "An intro to TypeScript",
"category": "TypeScript"
}

結果應該是一個 200 OK 響應代碼,同時響應體中會包含一個 JSON 對象,該對象表示已更新后的帖子。

刪除帖子

接下來,向 http://localhost:3000/posts/1 發送 DELETE 請求。預期結果是一個 200 OK 響應代碼,并且響應體中不包含任何 JSON 對象。

日志記錄

NestJS 使得在應用程序中添加日志記錄變得非常簡單。我們應該使用 NestJS 提供的日志記錄功能,而不是直接使用 console.log() 語句或原生的 Logger 類。NestJS 的日志記錄功能會在終端中為我們提供格式良好、易于閱讀的日志消息。

要在我們的 API 中添加日志記錄,第一步是在服務類中定義一個 logger 實例。

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);

// ...
}

現在我們已經定義了日志記錄器,接下來可以在服務類中添加日志語句。以下是一個日志語句的示例,我們可以將它作為 findAll() 方法的第一行代碼添加到 PostsService 類中。

this.logger.log('Returning all posts');

在客戶端向我們的 API 發起請求時,每次調用服務方法,這類日志語句都會在終端上輸出相應的日志消息,為我們提供便利。

當發送GET /posts請求時,我們應該在終端中看到以下消息。

[PostsService] Returning all posts.

Swagger 

NestJS 使得利用 NestJS Swagger 包將 OpenAPI 規范集成到我們的 API 中變得輕而易舉。OpenAPI 規范是一種用于描述 RESTful API 的標準,它主要用于文檔化和提供參考信息。

Swagger 設置

讓我們為 NestJS 安裝 Swagger。

npm install --save @nestjs/swagger swagger-ui-express

Swagger 配置

讓我們通過添加Swagger配置來更新引導NestJS應用程序的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();

當 NestJS 應用程序正在運行時,我們現在可以訪問 http://localhost:3000/api 來查看 API 的 Swagger 文檔。請注意,默認情況下,“default”會顯示在我們帖子相關路由的上方作為標簽。

為了改變這一點,我們可以在 PostsController 類中的 @Controller('posts') 裝飾器下方添加 @ApiTags('posts') 裝飾器。這將用 “posts” 替換 “default”,以清晰地表明這組端點屬于 “posts” 功能或特性集。

@Controller('posts')
@ApiTags('posts')

ApiProperty (api屬性)

為了使 PostModel 接口中的屬性對 Swagger 可見,我們需要使用 @ApiProperty() 裝飾器(對于必填字段)或 @ApiPropertyOptional() 裝飾器(對于可選字段)來注釋這些字段。但請注意,由于裝飾器通常用于類屬性,我們需要將接口更改為類,以便能夠應用這些裝飾器。

因此,我們的下一步是將 posts.interface.ts 文件中的 PostModel 接口轉換為類,并在相應的屬性上使用 @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()?裝飾器中,我們為每個字段指定了類型。值得注意的是,id?字段被標記為可選,因為在創建新的博客文章時,我們通常不知道它的?id?會是什么(通常由數據庫自動生成)。同時,date?字段被指定為使用?date-time?字符串格式。

這些更改使得 PostModel 的結構能夠在 Swagger 中得到正確的記錄。當 NestJS 應用程序運行時,我們可以訪問 http://localhost:3000/api 來查看 PostModel 的詳細文檔。

ApiResponse 接口

接下來,我們將利用 Swagger 的?@ApiResponse()?裝飾器來全面記錄 API 端點可能返回的所有響應類型。這樣做有助于我們的 API 用戶清晰地了解,通過調用特定的端點,他們可以獲得哪些類型的響應。我們將在?PostsController?類中實施這些更改。

對于 findAll 方法,我們將使用 @ApiOkResponse() 裝飾器來明確記錄 200 OK 成功響應的詳細信息。

@Get()
@ApiOkResponse({ description: 'Posts retrieved successfully.'})
public findAll(): Array<PostModel> {
return this.postsService.findAll();
}

對于 findOne 方法,當成功找到對應的帖子時,我們使用 @ApiOkResponse() 裝飾器來記錄 200 OK 響應。而當未找到帖子時,我們則使用 @ApiNotFoundResponse() 裝飾器來記錄 404 NOT FOUND HTTP 錯誤。

@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);
}

對于?create?方法,當成功創建一個新的帖子時,我們將使用?@ApiCreatedResponse()?裝飾器來記錄 201 CREATED 響應。而當檢測到重復的文章標題時,我們會使用?@ApiUnprocessableEntityResponse()?裝飾器來記錄一個 422 UNPROCESSABLE ENTITY HTTP 錯誤。

@Post()
@ApiCreatedResponse({ description: 'Post created successfully.' })
@ApiUnprocessableEntityResponse({ description: 'Post title already exists.' })
public create(@Body() post: PostModel): void {
return this.postsService.create(post);
}

對于?delete?方法,如果帖子被成功刪除,我們將使用?@ApiOkResponse()?裝飾器來記錄 200 OK 響應。而當嘗試刪除一個不存在的帖子時,我們會使用?@ApiNotFoundResponse()?裝飾器來記錄一個 404 NOT FOUND HTTP 錯誤。

@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);
}

對于 update 方法,當帖子被成功更新時,我們將使用 @ApiOkResponse() 裝飾器來記錄 200 OK 響應。如果嘗試更新一個不存在的帖子,我們會使用 @ApiNotFoundResponse() 裝飾器來記錄一個 404 NOT FOUND HTTP 錯誤。另外,當發現存在重復的帖子標題時,我們會使用 @ApiUnprocessableEntityResponse() 裝飾器來記錄一個 422 UNPROCESSABLE ENTITY HTTP 錯誤。

@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);
}

保存上述更改后,現在您應該能夠在 Swagger 網頁的 http://localhost:3000/api 地址上查看到每個端點的所有響應代碼及其相應的描述信息。

此外,我們可以利用 Swagger 來測試我們的 API,而不僅僅是依賴 Inclusive。只需在 Swagger 網頁上點擊每個端點下方的“Try it out”按鈕,即可輕松驗證這些端點是否按預期正常工作。

異常篩選器

異常過濾器為我們提供了對 NestJS 異常處理層的全面掌控。通過它,我們可以為 HTTP 異常響應主體添加自定義字段,或者記錄終端上發生的每個 HTTP 異常的日志信息。

接下來,我們需要在 /src/filters 文件夾中創建一個新的文件,命名為 http-exception.filter.ts。然后,在這個文件中,我們將定義一個異常過濾器類。

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); } }

這個類會利用 NestJS 的記錄器功能,在 HTTP 異常產生時向終端輸出警告信息。同時,當 HTTP 異常發生時,它還會在響應體中包含兩個自定義字段:timestamp?字段用于記錄異常發生的時間點,而?endpoint?字段則用于指明是哪個路由觸發了該異常。

為了將這個過濾器應用到 PostsController 上,我們需要使用 @UseFilters(HttpExceptionFilter) 裝飾器,并傳入 HttpExceptionFilter 類的一個新實例。

@Controller('posts')
@UseFilters(new HttpExceptionFilter())
export class PostsController {
constructor(private readonly postsService: PostsService) {}
}

保存這些更改后,NestJS將重新加載我們的應用程序。如果我們使用Inclusion向我們的API發送一個PUT /posts/1請求,它應該會觸發一個404 NOT FOUND HTTP錯誤,因為當它啟動時,我們的應用程序中不存在可供更新的博客文章。返回到Incubator的HTTP異常響應主體現在應該包含timestampendpoint字段。

{
"statusCode": 404,
"message": "Post not found.",
"timestamp": "2021-08-23T21:05:29.497Z",
"endpoint": "/posts/1"
}

我們還應該看到下面這行打印到終端。

WARN [HttpExceptionFilter] 404 Post not found.

總結

在本文中,我們深入了解了 NestJS 如何讓后端 API 開發變得迅速、簡潔且高效。NestJS 提供的應用程序結構助力我們構建出結構清晰、組織有序的項目。

我們涵蓋了很多內容,所以讓我們回顧一下我們學到的內容:

希望你在使用 NestJS 進行開發時能夠感受到它的強大與便捷,享受開發的樂趣!

原文鏈接:https://www.thisdot.co/blog/introduction-to-restful-apis-with-nestjs

上一篇:

使用Django REST Framework構建API——第二部分

下一篇:

使用Rust和Axum構建高性能REST API
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費