所有文章 > API开发 > 使用NestJS和Prisma构建REST API
使用NestJS和Prisma构建REST API

使用NestJS和Prisma构建REST API

NestJS 是一个著名的 Node.js 框架,它最近获得了很多开发人员的喜爱和关注。本文将教你如何使用 NestJS、Prisma、PostgreSQL 和 Swagger 构建后端 REST API

引言

在本教程中,您将学习如何为一个名为“Median”(一个简单的Medium克隆版博客应用)构建后端REST API。您将从创建一个新的NestJS项目开始。然后,您将启动自己的PostgreSQL服务器,并利用Prisma与之建立连接。最后,您将构建REST API并使用Swagger进行文档记录。

您将使用的技术

您将使用以下工具来构建此应用程序:

  • NestJS 作为后端框架
  • Prisma 作为对象关系映射器 (ORM)
  • PostgreSQL 作为数据库
  • Swagger 作为 API 文档工具
  • TypeScript 作为编程语言

先决条件

假定知识

这是一个适合初学者的教程。但假设您已具备以下基础:

  • 具备 JavaScript 或 TypeScript 的基本知识(首选)
  • 对 NestJS 有基础了解

注意:如果您不熟悉 NestJS,可以按照 NestJS 文档中的概述部分快速学习基础知识。

开发环境

为了学习本教程,您需要确保以下环境已就绪:

  • …已安装Node.js。
  • …已安装 Docker 或 PostgreSQL。
  • …已安装 Prisma VSCode 扩展。(可选)
  • …可以访问 Unix shell(如 Linux 和 macOS 中的终端/shell)来运行本系列中提供的命令。(可选)

注 1:安装Prisma VSCode扩展将提升您的编码体验,通过提供智能代码补全和语法高亮。

注 2:若您没有Unix shell环境(如Windows用户),您仍然可以跟随教程,但可能需要根据您的操作系统调整部分shell命令。

生成 NestJS 项目

首要步骤是安装 NestJS CLI(命令行接口)。在开发 NestJS 应用时,NestJS CLI 是一个不可或缺的工具。它集成了多种实用功能,能够助您轻松初始化、开发和维护 NestJS 应用。

接下来,利用 NestJS CLI 创建一个全新的空项目。只需在您期望存放项目的目录中执行以下命令:

npx @nestjs/cli new median

CLI将提示您为项目选择一个包管理器-选择npm。之后,您应该在当前目录中有一个新的NestJS项目。

在您喜欢的代码编辑器中打开项目(我们推荐VSCode)。您应该看到以下文件:

median
├── node_modules
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json

所处理的大部分代码都会存放在 src 目录中。NestJS CLI 已经为您预先创建了一些基础文件,其中几个尤为关键:

  • src/app.module.ts:这是应用程序的根模块,承载着整个应用的配置与组件。
  • src/app.controller.ts:这是一个基础控制器,包含了一个简单的路由 /,该路由会返回一个“Hello World!”的提示信息。
  • src/main.ts:这是应用程序的启动入口,负责启动并运行 NestJS 应用。

要启动项目,您可以执行以下命令:

npm run start:dev

该命令将监视您的文件,每当您进行更改时自动重新编译和重新加载服务器。要验证服务器是否正在运行,请转到URL http://localhost:3000/。您应该会看到一个空页面,其中包含消息'Hello World!'

注意:在学习本教程时,您应该保持服务器在后台运行。

创建 PostgreSQL 实例

本教程将指导您如何在计算机上通过 Docker 容器安装并运行 PostgreSQL,以作为 NestJS 应用的数据库。

注意:若您不打算使用 Docker,还可以选择在本地搭建 PostgreSQL 实例,或在 Heroku 上获取托管的 PostgreSQL 数据库服务。

首先,在项目的主文件夹中创建一个docker-compose.yml文件:

touch docker-compose.yml

这个 docker-compose.yml 文件是一个配置文件,它将包含运行docker容器的规范,其中包含PostgreSQL设置。在文件中创建以下配置:

# docker-compose.yml

version: '3.8'
services:

postgres:
image: postgres:13.5
restart: always
environment:
- POSTGRES_USER=myuser
- POSTGRES_PASSWORD=mypassword
volumes:
- postgres:/var/lib/postgresql/data
ports:
- '5432:5432'

volumes:
postgres:
volumes: postgres:

关于此配置,需要了解以下几点:

  • image 选项定义了要使用的 Docker 镜像。在这里,您使用的是 postgres 镜像的 13.5 版本。
  • environment 选项指定了在容器初始化期间传递给容器的环境变量。您可以在此处定义容器将使用的配置选项和机密信息,例如用户名和密码。
  • volumes 选项用于在主机文件系统中持久化数据。
  • ports 选项将主机上的端口映射到容器中的端口。格式遵循“主机端口:容器端口”的约定。在本例中,您将主机上的 5432 端口映射到 postgres 容器的 5432 端口。5432 是 PostgreSQL 传统上使用的端口。

请确保您的机器上没有其他服务正在使用 5432 端口。要启动 postgres 容器,请打开一个新的终端窗口,并在项目的主文件夹中运行以下命令:

docker-compose up

如果一切正常,新的终端窗口应显示数据库系统已准备好接受连接的日志。您应该会在终端窗口中看到类似于以下内容的日志:

...
postgres_1 | 2022-03-05 12:47:02.410 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
postgres_1 | 2022-03-05 12:47:02.410 UTC [1] LOG: listening on IPv6 address "::", port 5432
postgres_1 | 2022-03-05 12:47:02.411 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
postgres_1 | 2022-03-05 12:47:02.419 UTC [1] LOG: database system is ready to accept connections

恭喜 🎉。您现在已成功拥有了自己的 PostgreSQL 数据库!

注意:若您关闭终端窗口,容器也会随之停止。为避免这种情况,您可以在命令末尾添加 -d 选项,例如:docker-compose up -d。这样容器就会在后台持续运行,不会因终端关闭而中断。

设置 Prisma

既然数据库已经准备就绪,接下来就该配置 Prisma 了!

初始化 Prisma

首先,您需要将 Prisma CLI 安装为项目的开发依赖项。Prisma CLI 提供了多种命令,方便您与项目进行交互。

npm install -D prisma

您可以通过运行以下命令在项目中初始化 Prisma:

npx prisma init

这将创建一个新的 Prisma 目录和一个 schema.prisma 文件。这是包含数据库模式的主配置文件。这个命令也会在你的项目中创建一个.env 文件。

设置环境变量

在 .env 文件中,您会发现一个名为 DATABASE_URL 的环境变量,它包含一个虚拟的连接字符串。您需要将这个虚拟的连接字符串替换为您 PostgreSQL 实例的实际连接字符串。

// .env
DATABASE_URL="postgres://myuser:mypassword@localhost:5432/median-db"

注意:如果您没有使用 docker(如上一节所示)来创建 PostgreSQL 数据库,则您的连接字符串将与上面显示的连接字符串不同。关于PostgreSQL 的连接字符串格式,您可以在 Prisma 官方文档中查阅到。

了解 Prisma 架构

如果打开 prisma/schema.prisma,您应该看到以下默认模式:

// prisma/schema.prisma

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

schema.prisma 文件是使用 Prisma Schema 语言编写的,这是 Prisma 用于定义数据库架构的专有语言。该文件主要包含以下三个核心组成部分:

  • 数据源:指定数据库连接。上面的配置意味着您的数据库提供者是PostgreSQL,并且数据库连接字符串在 DATABASE_URL 环境变量中可用。
  • 生成器:表示您要生成 Prisma Client,这是数据库的类型安全查询生成器。它用于向数据库发送查询。
  • 数据模型:定义您的数据库模型。每个模型都将映射到底层数据库中的表。现在,您的 schema 中没有模型,您将在下一节中探索这部分。

注意:有关 Prisma 架构的更多信息,请查看 Prisma 文档。

对数据进行建模

现在,是时候为您的应用程序定义数据模型了。在本教程中,我们仅需要一个名为 Article 的模型,用以代表博客上的每一篇文章

请打开 prisma/schema.prisma 文件,并向其中添加一个全新的 Article 模型:

// prisma/schema.prisma

model Article {
id Int @id @default(autoincrement())
title String @unique
description String?
body String
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

您已经成功创建了一个功能完备的 Article 模型。该模型包含多个字段,如 idtitle 等,每个字段都具备名称、类型以及可选的特殊属性(例如 @id@unique 等)。通过在字段类型后添加 ?,您可以轻松地将某个字段设置为可选。

id字段被赋予了@id的特殊属性,这标志着它是该模型的主键。而 @default(autoincrement()) 属性则确保每当有新记录创建时,该字段都会自动递增并分配一个唯一的值。

published字段是指示文章是已发布还是处于草稿模式的标志。@default(false)属性表示默认情况下该字段应设置为false

两个DateTime 字段 createdAt updatedAt 将跟踪文章的创建时间和上次更新时间。@updatedAt属性会自动将该字段更新为当前时间戳,无论何时文章被修改,这个字段都会与当前时间同步更新。

迁移数据库

定义 Prisma 架构后,您将运行迁移以在数据库中创建实际表。要生成并执行您的第一个迁移,请在终端中运行以下命令:

npx prisma migrate dev --name "init"

此命令将执行三项操作:

  1. 保存迁移:Prisma Migrate会首先捕捉您数据库模式的当前快照,并据此分析出将您的数据库模式更新到最新状态所需的SQL命令。随后,Prisma会将这些SQL命令封装进迁移文件中,并将这些文件保存到新创建的 prisma/migrations 文件夹中。这个过程为您的数据库模式变更提供了详细的记录和步骤。
  2. 执行迁移:一旦迁移文件被创建,Prisma Migrate就能够执行这些文件中的SQL命令,从而在数据库中实际创建或更新底层表结构。这个过程确保了您的数据库模式与Prisma模式文件中的定义保持一致。
  3. 生成Prisma客户端:在迁移执行完毕后,Prisma会根据您最新的数据库模式自动生成Prisma客户端。如果您尚未安装客户端库,Prisma CLI也会为您自动安装。您可以在 package.json 文件的 dependencies 部分找到名为 @prisma/client 的包。Prisma Client是一个功能强大的TypeScript查询生成器,它根据您的Prisma模式文件自动生成与数据库交互的代码。这个客户端是为您的特定Prisma模式量身定制的,它将大大简化您向数据库发送查询的过程。

注意:您可以在 Prisma 文档中了解有关 Prisma Migrate 的更多信息。

如果成功完成,您应该会看到如下消息:

The following migration(s) have been created and applied from new schema changes:

migrations/
└─ 20220528101323_init/
└─ migration.sql

Your database is now in sync with your schema.
...
✔ Generated Prisma Client (3.14.0 | library) to ./node_modules/@prisma/client in 31ms

检查生成的迁移文件,了解 Prisma Migrate 在幕后的作用:

-- prisma/migrations/20220528101323_init/migration.sql

-- CreateTable
CREATE TABLE "Article" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"body" TEXT NOT NULL,
"published" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "Article_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "Article_title_key" ON "Article"("title");

注意:迁移文件的名称将略有不同。

这是在PostgreSQL数据库中创建 Article 表所需的SQL。它是由Prisma根据您的Prisma模式自动生成和执行的。

种子数据库

目前,数据库为空。因此,您将创建一个种子脚本,该脚本将使用一些虚拟数据填充数据库。

首先,创建一个名为prisma/seed.ts的文件,这个文件将包含初始化数据库种子所需的模拟数据和查询。

touch prisma/seed.ts

然后,在seed文件中添加以下代码:

// prisma/seed.ts

import { PrismaClient } from '@prisma/client';

// initialize Prisma Client
const prisma = new PrismaClient();

async function main() {
// create two dummy articles
const post1 = await prisma.article.upsert({
where: { title: 'Prisma Adds Support for MongoDB' },
update: {},
create: {
title: 'Prisma Adds Support for MongoDB',
body: 'Support for MongoDB has been one of the most requested features since the initial release of...',
description:
"We are excited to share that today's Prisma ORM release adds stable support for MongoDB!",
published: false,
},
});

const post2 = await prisma.article.upsert({
where: { title: "What's new in Prisma? (Q1/22)" },
update: {},
create: {
title: "What's new in Prisma? (Q1/22)",
body: 'Our engineers have been working hard, issuing new releases with many improvements...',
description:
'Learn about everything in the Prisma ecosystem and community from January to March 2022.',
published: true,
},
});

console.log({ post1, post2 });
}

// execute the main function
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
// close Prisma Client at the end
await prisma.$disconnect();
});

在这个脚本中,首先需要初始化Prisma客户端。接着,使用prisma.upsert()函数来创建两个项目。upsert函数只会在没有文章符合where条件时才创建一个新文章。您选择使用upsert查询而不是create查询,这是因为upsert可以避免因意外尝试两次插入相同记录而产生的错误。

您需要指定Prisma在执行种子命令时运行哪个脚本。您可以通过在 package.json 文件的末尾添加 prisma.seed 键来实现这一点:

// package.json

// ...
"scripts": {
// ...
},
"dependencies": {
// ...
},
"devDependencies": {
// ...
},
"jest": {
// ...
},
"prisma": {
"seed": "ts-node prisma/seed.ts"
}

seed 命令将执行您之前定义的 prisma/seed.ts 脚本。由于ts-node已经作为开发依赖安装在您的package.json中,这个命令应该能够自动运行。

请使用以下命令来执行数据库的种子数据初始化:

npx prisma db seed

您应该会看到以下输出:

Running seed command `ts-node prisma/seed.ts` ...
{
post1: {
id: 1,
title: 'Prisma Adds Support for MongoDB',
description: "We are excited to share that today's Prisma ORM release adds stable support for MongoDB!",
body: 'Support for MongoDB has been one of the most requested features since the initial release of...',
published: false,
createdAt: 2022-04-24T14:20:27.674Z,
updatedAt: 2022-04-24T14:20:27.674Z
},
post2: {
id: 2,
title: "What's new in Prisma? (Q1/22)",
description: 'Learn about everything in the Prisma ecosystem and community from January to March 2022.',
body: 'Our engineers have been working hard, issuing new releases with many improvements...',
published: true,
createdAt: 2022-04-24T14:20:27.705Z,
updatedAt: 2022-04-24T14:20:27.705Z
}
}

🌱 The seed command has been executed.

注意:您可以在 Prisma Docs 中了解有关种子设定的更多信息。

创建 Prisma 服务

在NestJS应用程序中,将Prisma Client API从应用逻辑中抽象出来是一个好习惯。为此,您将创建一个新的服务来封装Prisma Client。这个服务名为 PrismaService,它负责创建一个 PrismaClient 实例并连接到您的数据库。

Nest CLI提供了一种方便的方法,可以直接从命令行界面(CLI)生成模块和服务。在终端中运行以下命令:

npx nest generate module prisma
npx nest generate service prisma

注 1:如有需要,请参考 NestJS 文档了解服务和模块的介绍。

注 2:在某些情况下,在服务器已经运行的情况下运行 nest generate 命令可能会导致NestJS抛出异常:Error: Cannot find module './app.controller'。如果您遇到此错误,请从终端运行以下命令:rm -rf dist并重新启动服务器。

这应该会生成一个新的带有./src/prismaprisma.module.ts 文件的文件夹 prisma.service.ts。服务文件应包含以下代码:

// src/prisma/prisma.service.ts

import { INestApplication, Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient {}

Prisma模块将负责创建 PrismaService 的单例实例,并允许在整个应用程序中共享服务。要做到这一点,你需要将 PrismaService 添加到 exports 文件中的 prisma.module.ts 数组中:

// src/prisma/prisma.module.ts

import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}

现在,任何导入PrismaModule的模块都可以访问PrismaService,并将其注入到自己的组件/服务中。这是NestJS应用程序的常见模式。

完成后,您就完成了 Prisma 的设置!您现在可以开始构建 REST API。

设置 Swagger

Swagger 是一个基于 OpenAPI 规范来记录 API 的工具。Nest 提供了一个专门的 Swagger 模块,您很快就会用到它。

首先安装所需的依赖项:

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

现在打开 main.ts 并使用 SwaggerModule 类初始化Swagger:

// src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

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

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

当应用程序运行时,打开浏览器并导航到 http://localhost:3000/api. 您应该能够看到 Swagger UI 界面。

Swagger 用户界面

为Article模型实现 CRUD 操作

在本节中,您将为Article模型实现创建(Create)、读取(Read)、更新(Update)和删除(Delete)操作,以及任何相关的业务逻辑。

生成 REST 资源

在您能够实现REST API之前,您需要为Article模型生成REST资源。这可以通过Nest CLI快速完成。在终端中运行以下命令

npx nest generate resource

您将收到一些 CLI 提示。请相应地回答问题:

  1. 您想为这个资源使用什么名称(复数形式,例如,“users”)? articles
  2. 您使用的传输层是什么? REST API
  3. 您是否希望生成CRUD入口点? 是

现在您应该在src/articles目录下找到了所有REST端点的样板代码。在src/articles/articles.controller.ts文件中,您将看到不同路由(也称为路由处理器)的定义。处理每个请求的业务逻辑封装在src/articles/articles.service.ts文件中。目前,这个文件包含一些虚拟实现。

如果您再次打开 Swagger API 页面,您应该会看到如下内容:

自动生成的 “articles” 端点


SwaggerModule会搜索所有路由处理器上的@Body()、@Query()和@Param()装饰器,以生成这个API页面。

将PrismaClient添加到Articles模块

要在Articles模块内访问PrismaClient,您必须将PrismaModule作为一个导入添加。在ArticlesModule中添加以下导入:

// src/articles/articles.module.ts

import { Module } from '@nestjs/common';
import { ArticlesService } from './articles.service';
import { ArticlesController } from './articles.controller';
import { PrismaModule } from 'src/prisma/prisma.module';

@Module({
controllers: [ArticlesController],
providers: [ArticlesService],
imports: [PrismaModule],
})
export class ArticlesModule {}

您现在可以将 PrismaService 注入到 ArticlesService 中,并使用它来访问数据库。为此,您需要在articles.service.ts文件中添加一个构造函数,如下所示:

// src/articles/articles.service.ts

import { Injectable } from '@nestjs/common';
import { CreateArticleDto } from './dto/create-article.dto';
import { UpdateArticleDto } from './dto/update-article.dto';
import { PrismaService } from 'src/prisma/prisma.service';

@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) {}

// CRUD operations
}

定义 GET /articles 终端节点

该端点的控制器名为 findAll。此端点将返回数据库中所有已发布的文章。findAll 控制器的实现如下所示:

// src/articles/articles.controller.ts

@Get()
findAll() {
return this.articlesService.findAll();
}

您需要更新 ArticlesService.findAll()以返回数据库中所有已发布文章的数组:

// src/articles/articles.service.ts

@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) {}

create(createArticleDto: CreateArticleDto) {
return 'This action adds a new article';
}

findAll() {
return `This action returns all articles`;
return this.prisma.article.findMany({ where: { published: true } });
}

findMany 查询将返回与 article 条件匹配的所有 where 记录。

您可以通过访问 http://localhost:3000/api 并点击 GET /articles 菜单来测试这个端点。点击“Try it out”,然后点击“Execute”按钮来查看结果。

注意:您也可以直接在浏览器中运行所有请求,或者通过REST客户端(如Postman)运行所有请求。Swagger还为每个请求生成curl命令,以防您希望在终端中运行HTTP请求。

定义 GET /articles/drafts 终端节点

您将定义一个新的路由来获取所有未发布的文章。NestJS 没有为这个端点自动生成控制器路由处理器,因此您需要自行编写它。

// src/articles/articles.module.ts

import { Module } from '@nestjs/common';
import { ArticlesService } from './articles.service';
import { ArticlesController } from './articles.controller';
import { PrismaModule } from 'src/prisma/prisma.module';

@Module({
controllers: [ArticlesController],
providers: [ArticlesService],
imports: [PrismaModule],
})
export class ArticlesModule {}

你的编辑器应该显示一个错误,提示没有名为articlesService.findDrafts()的函数存在。要解决这个问题,请在 findDrafts 中实现 ArticlesService 方法:

// src/articles/articles.service.ts

@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) {}

create(createArticleDto: CreateArticleDto) {
return 'This action adds a new article';
}

findDrafts() {
return this.prisma.article.findMany({ where: { published: false } });
}

// ...

}

GET /articles/drafts 端点现在应该已经在 Swagger API 页面中可用了。

注意:我建议您在完成实施后通过 Swagger API 页面测试每个终端节点。

定义 GET /articles/:id 终端节点

此端点的控制器路由处理器名为findOne。它的实现如下所示:

// src/articles/articles.controller.ts

@Get(':id')
findOne(@Param('id') id: string) {
return this.articlesService.findOne(+id);
}

路由接受一个动态的id参数,该参数被传递给findOne控制器路由处理程序。由于Article模型具有整数id字段,因此需要使用id运算符将+参数转换为数字。

现在,更新 findOne 中的 ArticlesService 方法,以返回具有给定id的文章:

// src/articles/articles.service.ts
@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) {}

create(createArticleDto: CreateArticleDto) {
return 'This action adds a new article';
}

findAll() {
return this.prisma.article.findMany({ where: { published: true } });
}

findOne(id: number) {
return `This action returns a #${id} article`;
return this.prisma.article.findUnique({ where: { id } });
}
}

请访问 http://localhost:3000/api,然后点击 GET /articles/{id} 下拉菜单。点击“Try it out”(试用),在 id 参数中输入一个有效的值,接着点击“Execute”(执行)来查看结果。

定义POST /articles终端节点

这是创建新文章的端点,此端点的控制器路由处理程序称为 create。它的实现如下所示:

// src/articles/articles.controller.ts

@Post()
create(@Body() createArticleDto: CreateArticleDto) {
return this.articlesService.create(createArticleDto);
}

请注意,它在请求体中期望一个类型为CreateArticleDto的参数。DTO(Data Transfer Object,数据传输对象)是一个定义数据如何通过网络发送的对象。目前,CreateArticleDto是一个空类。您需要向其中添加属性,以定义请求正文的结构。复制再试一次分享

// src/articles/dto/create-article.dto.ts

import { ApiProperty } from '@nestjs/swagger';

export class CreateArticleDto {
@ApiProperty()
title: string;

@ApiProperty({ required: false })
description?: string;

@ApiProperty()
body: string;

@ApiProperty({ required: false, default: false })
published?: boolean = false;
}

需要使用 @ApiProperty 装饰器来使类属性对 SwaggerModule 可见。更多关于这方面的信息可以在NestJS文档中找到。

现在,您应该在Swagger API页面的Schemas下定义 CreateArticleDtoUpdateArticleDto 的结构是从 CreateArticleDto 定义中自动推断出来的。因此,UpdateArticleDto 也在Swagger中进行了定义。

现在更新create中的ArticlesService方法,在数据库中创建一个新项目:

// src/articles/articles.service.ts

@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) {
}

create(createArticleDto: CreateArticleDto) {
return 'This action adds a new article';
return this.prisma.article.create({ data: createArticleDto });
}

// ...
}

定义 PATCH /articles/:id 终端节点

此端点用于更新现有文章,此端点的路由处理程序称为 update。它的实现如下所示:

// src/articles/articles.controller.ts

@Patch(':id')
update(@Param('id') id: string, @Body() updateArticleDto: UpdateArticleDto) {
return this.articlesService.update(+id, updateArticleDto);
}

updateArticleDto 定义被定义为 PartialType 中的 CreateArticleDto。所以它可以拥有 CreateArticleDto 的所有属性。

// src/articles/dto/update-article.dto.ts

import { PartialType } from '@nestjs/swagger';
import { CreateArticleDto } from './create-article.dto';

export class UpdateArticleDto extends PartialType(CreateArticleDto) {}

与之前一样,您必须更新此操作对应的服务方法:

// src/articles/articles.service.ts

@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) {}

// ...

update(id: number, updateArticleDto: UpdateArticleDto) {
return `This action updates a #${id} article`;
return this.prisma.article.update({
where: { id },
data: updateArticleDto,
});
}

// ...
}

article.update 操作将尝试使用给定的 Article 查找 id 记录,并使用 updateArticleDto的数据更新它。

如果在数据库中找不到此类 Article 记录,Prisma 将返回错误。在这种情况下,API 不会返回用户友好的错误消息。您将在以后的教程中学习NestJS的错误处理。

定义DELETE /articles/:id终端节点

此端点用于删除现有文章,此端点的路由处理程序称为 remove。它的实现方法如下所示:

// src/articles/articles.controller.ts

@Delete(':id')
remove(@Param('id') id: string) {
return this.articlesService.remove(+id);
}

就像之前一样,转到 ArticlesService 并更新相应的方法:

// src/articles/articles.service.ts

@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) { }

// ...

remove(id: number) {
return `This action removes a #${id} article`;
return this.prisma.article.delete({ where: { id } });
}
}

这是针对 articles 端点的最后一个操作。恭喜您的API即将就绪!🎉

在 Swagger 中将终端节点分组在一起

@ApiTags 类中添加一个ArticlesController装饰器,以便在Swagger中将所有articles端点分组在一起:

// src/articles/articles.controller.ts

import { ApiTags } from '@nestjs/swagger';

@Controller('articles')
@ApiTags('articles')
export class ArticlesController {
// ...
}

现在,该API页面已将articles端点分组在一起。

更新 Swagger 响应类型

如果您查看 Swagger 中每个端点下的“Responses”选项卡,您会发现“Description”是空的。这是因为 Swagger 还不知道任何端点的响应类型。您将使用一些装饰器来解决这个问题。

首先,您需要定义一个实体,Swagger 可以使用它来标识返回的实体对象的结构。为此,请更新 articles.entity.ts 文件中的 ArticleEntity 类,如下所示:

// src/articles/entities/article.entity.ts

import { Article } from '@prisma/client';
import { ApiProperty } from '@nestjs/swagger';

export class ArticleEntity implements Article {
@ApiProperty()
id: number;

@ApiProperty()
title: string;

@ApiProperty({ required: false, nullable: true })
description: string | null;

@ApiProperty()
body: string;

@ApiProperty()
published: boolean;

@ApiProperty()
createdAt: Date;

@ApiProperty()
updatedAt: Date;
}

这是Prisma Client生成的Article类型的实现,每个属性都添加了@ApiProperty装饰器。

现在,是时候使用正确的响应类型对控制器路由处理程序进行注释了。NestJS 有一组用于此目的的装饰器。

// src/articles/articles.controller.ts

import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { ArticleEntity } from './entities/article.entity';

@Controller('articles')
@ApiTags('articles')
export class ArticlesController {
constructor(private readonly articlesService: ArticlesService) {}

@Post()
@ApiCreatedResponse({ type: ArticleEntity })
create(@Body() createArticleDto: CreateArticleDto) {
return this.articlesService.create(createArticleDto);
}

@Get()
@ApiOkResponse({ type: ArticleEntity, isArray: true })
findAll() {
return this.articlesService.findAll();
}

@Get('drafts')
@ApiOkResponse({ type: ArticleEntity, isArray: true })
findDrafts() {
return this.articlesService.findDrafts();
}

@Get(':id')
@ApiOkResponse({ type: ArticleEntity })
findOne(@Param('id') id: string) {
return this.articlesService.findOne(+id);
}

@Patch(':id')
@ApiOkResponse({ type: ArticleEntity })
update(@Param('id') id: string, @Body() updateArticleDto: UpdateArticleDto) {
return this.articlesService.update(+id, updateArticleDto);
}

@Delete(':id')
@ApiOkResponse({ type: ArticleEntity })
remove(@Param('id') id: string) {
return this.articlesService.remove(+id);
}
}

您为 @ApiOkResponseGETPATCH 端点添加了 DELETE ,为 @ApiCreatedResponse 端点添加了 POSTtype属性用于指定返回类型,您可以在NestJS文档中找到NestJS提供的所有响应装饰器。

现在,Swagger 应该正确定义 API 页面上所有端点的响应类型。

总结和结束语

恭喜!您已经成功使用NestJS构建了一个基础的REST API。在本教程中,您完成了以下内容:

  • 使用 NestJS 构建 REST API
  • 将 Prisma 顺利集成到 NestJS 项目中
  • 使用 Swagger 和 OpenAPI 记录了您的 REST API

本教程的一个重要收获是,使用NestJS和Prisma构建REST API是多么的简单。这是一个非常高效的堆栈,用于快速构建结构良好、类型安全且可维护的后端应用程序。

您可以在GitHub上找到本项目的源代码。如果您在使用过程中遇到任何问题,欢迎随时在仓库中提出问题或提交PR。

原文链接:https://www.prisma.io/blog/nestjs-prisma-rest-api-7D056s1BmOL0

#你可能也喜欢这些API文章!