使用 NestJS 和 Prisma 创建 REST API
原文标题:Building a REST API with NestJS and Prisma
NestJS 是著名的 Node.js 框架之一,获得了越来越多的开发者的喜爱和关注。本文将教你如何使用NestJS、Prisms、Postgres 和 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 文档中的快速上手一章来快速学习它的基础知识。
开发环境
按照提供的示例进行操作,你需要:
- 安装 Nede.js。
- 安装 Docker 和 PostgreSQL。
- 安装 Prisma VSCode 扩展插件。(可选)
- 可以访问 Unix shell(如 Linux 和 macOS 中的终端/命令行)来运行本系列中提供的命令。(可选)
提示1:可选的 Prisma VSCode 扩展插 Prisma 添加了一些非常棒的智能感知和语法高亮功能。
提示2:如果你没有 Unix shell(比如,你在 Windows 机器上开发),你仍可以参照这些,但是需要将命令修改为适应你机器的。
生成 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:3030/ 。你应该可以看到一个只有 “Hello world!” 信息的空页面。
提示:当你阅读本教程时你最好保持这个服务一直在后台运行。
创建一个 PostgreSQL 实例
你将使用 PostgreSQL 作为 NestJS 应用的数据库。本教程会教你如何在你本机的 Docker 容器上安装并运行 PostgreSQL。
提示:如果你不想使用 Docker,你可以在本地安装一个 PostgreSQL 实例或者在 Heroku 上面获得一台部署好 PostgreSQL 数据库。
首先,在你的项目主目录中创建一个 docker-compose.yml 文件:
touch docker-compose.yml
这个 docker-compose.yml 文件是一个配置文件,它包含了运行带有 PostgreSQL 设置的 docker 容的规范。在文件中创建以下配置:
# docker-compose.yml
version: '3.8'
services:
# Docker connection string: postgres://postgres:postgres@localhost:5432/
postgres:
image: postgres:13.5
restart: always
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- postgres:/var/lib/postgresql/data
ports:
- '5432:5432'
volumes:
postgres:
关于此配置需要了解的一些事项:
image选项定义了要使用的 Docker 镜像。在这里,你可以使用这个13.5版的 postgres 镜像。environment选项声明了在初始化期间传入的环境变量。你可以定义配置项和密码 - 比如用户名和密码 - 容器会在这里使用。- volumes 选项用于在主机文件系统中持久化数据。
- ports 选项映射从宿主机到容器的端口。其格式遵循
‘host_port:container_port’的约定。在本例中,你将映射宿主机的5432端口到postgres容器的5432端口。PostgreSQL 通常使用的端口就是5432。
确保你本机的 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
你可以通过运行以下命令来初始化你的项目:
npx prisma init
这将创建一个新的 prisma 文件夹,里面包含一个 schema.prisma 文件。这个就是包含你数据库架构的主要配置文件。此命令同时也会在你的项目内创建一个 .env 文件。
设置环境变量
在 .env 文件里,你应该看到一个带有虚假连接字符串的 DATABASE_URL 环境变量。使用你自己的 PostregreSQL 实例来替换这个连接字符串。
// .env
DATABASE_URL="postgres://postgres:postgres@localhost:5432/median-db"
提示:如果你不用 docker(如上一张所述)来创建你的 PostgreSQL 数据库,你的连接字符串将和以上所示的不同。你可以在 Prisma Docs 中找到正确的 PostgreSQL 连接字符串格式。
理解 Prisma schema
当你打开 prisma/schema.prisma,你将看到以下的默认 schema 内容:
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
该文件使用 Prisma Schema Language 书写,这是 Prisma 用来定义数据库模式的一种语言。schema.prisma 文件主要包含三个组件:
- 数据源:定义你的数据库连接。上面配置表示你的数据库提供者是 PostgreSQL,并且数据库连接字符串存在
DATABASE_URL环境变量中。 - 生成器:表示你要生成 Prisma Client,这是一个类型安全的数据库查询构建器。它用于向你的数据库发送查询语句。
- 数据模型:定义你的数据库模型。每一个模型都将被映射为底层数据库中的一张表。现在你的 schema 中没有任何模型,你将在下一章节中探索这部分内容。
提示:要了解更多关于 Prisma schema 的信息,可以查看 Prisma docs。
数据建模
现在是时候为你的应用定义数据模型了。对于本教程,你只需要一个 Article 模型来表示博客上的每篇文章。
在 prisma/prisma.schema 文件里,为你的 schema 添加一个名为 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 模型。每个字段都有一个名称(id、title 等),一个类型(Int、String 等),还有其他可选的属性(@id、@unique 等)。你可以通过在字段类型后添加一个 ? 来使该字段变得可选。
id 字段有一个特定的属性叫 @id。这个属性表示该字段是模型的主键。@default(autoincrement()) 属性表示该字段应自动递增并分配给任何新创建的记录。
published 字段是一个标记,用于表示文章是已发布还是处于草稿状态。@default(false) 属性表示该字段默认会被设置为 false。
那两个 DateTime 字段,createdAt 和 updatedAt,将跟踪文章的创建时间和最近一次更新时间。当文章被修改时, @updatedAt 属性将使用当前时间戳自动更新该字段。每当文章修改时就会使用当前时间戳为该字段添加日期。
迁移数据库
定义 Prisma schema 后,你将运行迁移脚本以在数据库中创建实体表。要生成并执行您的第一次迁移,请在终端中运行以下命令:
npx prisma migrate dev --name "init"
该命令将会做三件事:
- 保存迁移:Prisma Migrate 将对你的 schema 创建快照,并生成执行迁移所需的 SQL 命令。Prisma 会将包含 SQL 命令的迁移文件保存到一个新建的
prisma/migrations文件夹内。 - 执行迁移:Prisma Migrate 将执行迁移文件中的 SQL 命令以在数据库中创建底层数据表。
- 生成 Prisma Client:Prisma 将会基于你最新的 schema 文件生成 Prisma Client。如果你本地没有安装 Client 库文件,CLI 会自动为你安装。你会在
package.json文件的dependencies中看到@prisma/client包。Prisma Client 是一个基于你的 Prisma schema 自动生成的 TypeScript 查询构建器。
提示:你可以在 Prisma docs 文档中学习更多 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 schema 自动生成和执行的。
数据填充
目前为止,数据库还是空的。所以你需要创建一个种子脚本,该脚本将使用一些虚拟数据填充数据库。
首先,创建一个名为 prisma/seed.ts 的种子文件。该文件将包含填充数据库所需要的虚拟数据和查询语句。
touch prisma/seed.ts
然后,在种子文件内,添加以下代码:
// 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 Client。然后通过 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,它将负责实例化 Prisma Client 实例并连接你的数据库。
Nest CLI 给你提供了一个简单的方法,可以在命令行中直接创建模块和服务。在你的终端中运行以下命令:
npx nest generate module prisma
npx nest generate service prisma
提示2:在某些情况下,当服务器已经运行时执行
nest generate命令,可能会导致 NestJS 抛出异常:Error: Cannot find module './app.controller’。如果你遇到这个错误,需要在终端中运行以下命令:rm -rf dist并且重启服务器。
这将生成一个包含 prisma.module.ts 和 prisma.service.ts 的新的子文件夹 .src/prisma。使用以下代码更新服务文件:
// src/prisma/prisma.service.ts
import { INestApplication, Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient {
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close();
});
}
}
为确保你的应用程序能够优雅地关闭,enableShutdownHooks 的定义是必要的。NestJS 文档中提供了更多信息。
Prisma 模块将负责创建 PrismaService 的单例实例,并允许在整个应用中共享服务。要做到这点,你需要把 PrismaService 添加到 prisma.module.ts 文件的 exports 数组中:
// 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。

为 Article 模型实现 CRUD 操作
在本节中,你将为 Article 模型和其附带的业务逻辑实现创建、读取、更新和删除(CRUD)操作。
生成 REST 资源
在实现 REST API 之前,你还需要为 Article 模型生成 REST 资源。使用 Nest CLI 可以快速完成。在终端中运行以下命令:
npx nest generate resource
你将收到一些 CLI 提示。相应地回答问题:
What name would you like to use for this resource (plural, e.g., "users")?articlesWhat transport layer do you use?REST APIWould you like to generate CRUD entry points?Yes
你现在应该发现了一个新的 src/articles 目录,其中包含有 REST 端点的所有模版。在 src/articles/articles.controller.ts 文件中,你会看到不能路由的定义(也叫做路由处理器)。处理每一个请求的业务逻辑被封装在 src/articles/articles.service.ts 文件中。目前,该文件也包含了虚拟数据的实现。
如果你再次打开 Swagger API 页面,你会看到以下所示内容:

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 {}
现在你可以在 ArticlesService 中注入 PrismaService 并使用它来访问数据库。为此,在 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 查询将返回符合 where 条件的所有 article 记录。
你可以通过访问 http://localhost:3000/api 并点开 GET/articles 下拉按钮来测试端点。按 Try it out,然后按 Execute 来查看结果。

注意:你也可以直接在浏览器或者通过一个 REST 客户端(像 Postman)来运行所有的请求。Swagger也同样为每个请求生成了 curl 命令,以便你在终端中运行 HTTP 请求。
定义 GET /articles/drafts 端点
你将定义一个新的路由来获取所有未发布的文章。NestJS 没有为这个端点自动生成路由控制器,所以你需要自己写:
// src/articles/articles.controller.ts
@Controller('articles')
export class ArticlesController {
constructor(private readonly articlesService: ArticlesService) {}
@Post()
create(@Body() createArticleDto: CreateArticleDto) {
return this.articlesService.create(createArticleDto);
}
+ @Get('drafts')
+ findDrafts() {
+ return this.articlesService.findDrafts();
+ }
// ...
}
你的编辑器应该会报错,因为 articlesService.findDrafts() 函数不存在。因此,需要在 ArticlesService 中实现 findDrafts 这个函数:
// 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 路由控制器。由于 Articles 模型具有整数 id 字段,因此需要使用需要使用 + 运算符将 id 参数转换成数字。
现在,更新 ArticlesService 中的 findOne 方法来返回具有给定 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(数据传输对象)是一个定义数据如何在网络中传输的对象。目前,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 文档。
CreateArticleDto 现在应该在 Swagger API 页面中的 Schemas 下定义了。UpdateArticleDto 的规范是从 CreateArticleDto 中自动推断出来的。所以 UpdateArticleDto 也已经被 Swagger 定义好了。

现在更新 ArticlesService 中的 create 方法在数据库中创建一篇新文章。
// 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 被定义为 CreateArticleDto 的 PartialType。所以它也具有 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 操作将会查找具有给定 id 的 Article 记录,并使用 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);
}
正如之前一样,在 ArticleService 中更新相应的方法:
// 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 中将端点组合在一起
向 ArticlesController 类中添加 @ApiTags 装饰器,在 Swagger 中将所有 articles 的端点合并成组。
// src/articles/articles.controller.ts
import { ApiTags } from '@nestjs/swagger';
@Controller('articles')
@ApiTags('articles')
export class ArticlesController {
// ...
}
API 页面现在已经把 articles 端点组合到一起了。
更新 Swagger 响应类型
如果你查看 Swagger 中每个端点下的 Response 选项卡,你会发现 Description 还是空的。这是因为 Swagger 不知道任何端点的响应类型。你将使用一些装饰器来解决此问题。
首先,你需要定义一个实体,Swagger 可以使用该实体来识别返回的实体对象的形状。为此,请更新 article.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);
}
}
你已经为 GET、PATCH 和 DELETE 端点添加了 @ApiOkResponse,为 POST 端点添加了 @ApiCreateResponse。type 属性被用来指定返回类型。你可以在 NestJS 官方提供的 NestJS 文档里找到所有的响应装饰器。
现在,Swagger 已经为 API 页面上的所有端点都定义好了响应类型。

总结和最后的评论
恭喜!你已经使用 NestJS 构建了一个基本的 REST API。在本系列中,你:
- 使用 NestJS 构建了 REST API
- 在 NestJS 项目中完美集成了 Prisma
- 使用 Swagger 和 Open API 文档化你的 REST API
本教程的主要内容之一是告诉你使用 NestJS 和 Prisma 构建 REST API 是多么容易。这是一个非常高效的技术栈,可用于快速构建结构良好、类型安全且可维护的后端应用程序。
你可以在 Github 上找到这个项目的源码。如果你发现问题,请随时在仓库中提出问题或提交 PR。
原文作者:Tasin Ishmam Backend web developer
原文地址:www.prisma.io/blog/nestjs…
原文发表于:2022年6月03日