NestJS 是一个卓越的 Node.js 框架,最近获得了开发者的广泛喜爱和关注。本文将教您如何使用 NestJS、Prisma、PostgreSQL 和 Swagger 构建后端 REST API。
目录
- 简介
- 先决条件
- 生成 NestJS 项目
- 创建 PostgreSQL 实例
- 设置 Prisma
- 设置 Swagger
- 为
Article模型实现 CRUD 操作 - 更新 Swagger 响应类型
- 总结与最终说明
简介
在本教程中,您将学习如何为一个名为“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 扩展为 Prisma 提供了非常出色的智能感知和语法高亮。
注意 2:如果您没有 Unix shell(例如,您在 Windows 机器上),您仍然可以遵循本教程,但 shell 命令可能需要根据您的机器进行修改。
生成 NestJS 项目
您首先需要安装 NestJS CLI。NestJS CLI 在处理 NestJS 项目时非常方便。它内置了实用工具,可帮助您初始化、开发和维护 NestJS 应用程序。
您可以使用 NestJS CLI 创建一个空项目。首先,在您希望项目所在的目录中运行以下命令:
CLI 将提示您为项目选择一个包管理器——请选择 npm。之后,您应该在当前目录中拥有一个新的 NestJS 项目。
在您首选的代码编辑器中打开项目(我们推荐 VSCode)。您应该看到以下文件:
您大部分的代码都将位于 src 目录中。NestJS CLI 已经为您创建了一些文件。其中一些值得注意的文件是:
src/app.module.ts:应用程序的根模块。src/app.controller.ts:一个基本的控制器,包含一个路由:/。此路由将返回一个简单的'Hello World!'消息。src/main.ts:应用程序的入口点。它将启动 NestJS 应用程序。
您可以使用以下命令启动您的项目:
此命令将监视您的文件,并在您进行更改时自动重新编译和重新加载服务器。要验证服务器是否正在运行,请访问 URL https://:3000/。您应该会看到一个空白页面,上面显示着消息 'Hello World!'。
注意:在您学习本教程时,应保持服务器在后台运行。
创建 PostgreSQL 实例
您将使用 PostgreSQL 作为 NestJS 应用程序的数据库。本教程将向您展示如何通过 Docker 容器在您的机器上安装和运行 PostgreSQL。
注意:如果您不想使用 Docker,您可以原生设置 PostgreSQL 实例或在Heroku 上获取托管的 PostgreSQL 数据库。
首先,在项目主文件夹中创建一个 docker-compose.yml 文件:
这个 docker-compose.yml 文件是一个配置文件,它将包含运行一个内置 PostgreSQL 的 Docker 容器的规范。在文件中创建以下配置:
关于此配置,有几点需要理解:
- The
image选项定义要使用的 Docker 镜像。这里,您正在使用postgres镜像版本 13.5。 - The
environment选项指定在初始化期间传递给容器的环境变量。您可以在此处定义容器将使用的配置选项和秘密——例如用户名和密码。 - The
volumes选项用于在主机文件系统中持久化数据。 - The
ports选项将主机机器的端口映射到容器。格式遵循'host_port:container_port'约定。在这种情况下,您将主机机器的端口5432映射到postgres容器的端口5432。5432通常是 PostgreSQL 使用的端口。
确保您的机器的端口 5432 上没有运行任何其他程序。要启动 postgres 容器,请打开一个新的终端窗口并在项目主文件夹中运行以下命令:
如果一切正常,新的终端窗口应该显示数据库系统已准备好接受连接的日志。您应该在终端窗口中看到与以下内容相似的日志:
恭喜 🎉!您现在拥有了自己的 PostgreSQL 数据库可以操作了!
注意:如果您关闭终端窗口,它也将停止容器。您可以通过在命令末尾添加
-d选项来避免这种情况,例如:docker-compose up -d。这将在后台无限期地运行容器。
设置 Prisma
数据库已准备就绪,现在是时候设置 Prisma 了!
初始化 Prisma
首先,安装 Prisma CLI 作为开发依赖。Prisma CLI 将允许您运行各种命令并与您的项目进行交互。
您可以通过运行以下命令在项目中初始化 Prisma:
这将创建一个新的 prisma 目录,其中包含 schema.prisma 文件。这是包含您数据库模式的主要配置文件。此命令还会在您的项目中创建一个 .env 文件。
设置您的环境变量
在 .env 文件中,您应该会看到一个 DATABASE_URL 环境变量,其中包含一个虚拟连接字符串。将其替换为您的 PostgreSQL 实例的连接字符串。
注意:如果您没有使用 Docker(如上一节所示)来创建 PostgreSQL 数据库,您的连接字符串将与上面所示的不同。PostgreSQL 的连接字符串格式可在Prisma 文档中找到。
理解 Prisma 模式
如果您打开 prisma/schema.prisma,您应该会看到以下默认模式:
此文件采用 Prisma 模式语言编写,Prisma 使用该语言定义您的数据库模式。schema.prisma 文件包含三个主要组成部分:
- 数据源:指定您的数据库连接。上述配置意味着您的数据库提供者是 PostgreSQL,并且数据库连接字符串可在
DATABASE_URL环境变量中找到。 - 生成器:指示您要生成 Prisma Client,这是一个用于您数据库的类型安全查询构建器。它用于向数据库发送查询。
- 数据模型:定义您的数据库模型。每个模型都将映射到底层数据库中的一个表。目前您的模式中没有模型,您将在下一节中探讨这部分内容。
注意:有关 Prisma 模式的更多信息,请查阅Prisma 文档。
数据建模
现在是时候为您的应用程序定义数据模型了。对于本教程,您只需要一个 Article 模型来表示博客上的每篇文章。
在 prisma/prisma.schema 文件中,为您的模式添加一个名为 Article 的新模型:
这里,您创建了一个包含多个字段的 Article 模型。每个字段都有一个名称(id、title 等)、一个类型(Int、String 等)以及其他可选属性(@id、@unique 等)。通过在字段类型后添加 ? 可以使字段变为可选。
The id 字段有一个名为 @id 的特殊属性。此属性表示该字段是模型的主键。@default(autoincrement()) 属性表示该字段应自动递增并分配给任何新创建的记录。
The published 字段是一个标志,用于指示文章是已发布还是处于草稿模式。@default(false) 属性表示此字段默认应设置为 false。
两个 DateTime 字段 createdAt 和 updatedAt 将跟踪文章的创建时间和上次更新时间。@updatedAt 属性将在文章修改时自动用当前时间戳更新该字段。
迁移数据库
定义好 Prisma 模式后,您将运行迁移以在数据库中创建实际的表。要生成并执行您的第一次迁移,请在终端中运行以下命令:
此命令将执行三件事:
- 保存迁移:Prisma Migrate 将对您的模式进行快照,并计算出执行迁移所需的 SQL 命令。Prisma 会将包含 SQL 命令的迁移文件保存到新创建的
prisma/migrations文件夹中。 - 执行迁移:Prisma Migrate 将执行迁移文件中的 SQL,以在您的数据库中创建底层表。
- 生成 Prisma Client:Prisma 将根据您最新的模式生成 Prisma Client。由于您尚未安装 Client 库,CLI 也会为您安装。您应该会在
package.json文件的dependencies中看到@prisma/client包。Prisma Client 是一个从您的 Prisma 模式自动生成的 TypeScript 查询构建器。它针对您的 Prisma 模式进行了定制,并将用于向数据库发送查询。
注意:您可以在Prisma 文档中了解更多关于 Prisma Migrate 的信息。
如果成功完成,您应该会看到类似以下的消息:
检查生成的迁移文件,以了解 Prisma Migrate 在幕后做了什么:
注意:您的迁移文件名会略有不同。
这是在您的 PostgreSQL 数据库中创建 Article 表所需的 SQL。它是 Prisma 根据您的 Prisma 模式自动生成和执行的。
填充数据库
当前数据库是空的。因此,您将创建一个种子脚本,用一些虚拟数据填充数据库。
首先,创建一个名为 prisma/seed.ts 的种子文件。此文件将包含填充数据库所需的虚拟数据和查询。
然后,在种子文件中添加以下代码:
在此脚本中,您首先初始化 Prisma Client。然后,您使用 prisma.upsert() 函数创建两篇文章。upsert 函数仅在没有文章与 where 条件匹配时才创建新文章。您使用 upsert 查询而不是 create 查询,因为 upsert 可以避免意外尝试插入相同记录两次所产生的错误。
您需要告诉 Prisma 在运行种子命令时执行哪个脚本。您可以通过将 prisma.seed 键添加到 package.json 文件末尾来完成此操作:
The seed 命令将执行您之前定义的 prisma/seed.ts 脚本。此命令应该自动工作,因为 ts-node 已作为开发依赖安装在您的 package.json 中。
使用以下命令执行数据填充:
您应该会看到以下输出:
注意:您可以在Prisma 文档中了解更多关于数据填充的信息。
创建 Prisma 服务
在您的 NestJS 应用程序中,最佳实践是将 Prisma Client API 从应用程序中抽象出来。为此,您将创建一个包含 Prisma Client 的新服务。这个名为 PrismaService 的服务将负责实例化 PrismaClient 实例并连接到您的数据库。
Nest CLI 提供了一种从命令行直接生成模块和服务的方式。在您的终端中运行以下命令:
注意 2:在某些情况下,当服务器已在运行时运行
nest generate命令,可能导致 NestJS 抛出异常,提示:Error: Cannot find module './app.controller'。如果您遇到此错误,请在终端中运行以下命令:rm -rf dist,然后重新启动服务器。
这应该会生成一个新的子目录 ./src/prisma,其中包含 prisma.module.ts 和 prisma.service.ts 文件。服务文件应包含以下代码:
Prisma 模块将负责创建 单例实例的 PrismaService,并允许在整个应用程序中共享该服务。为此,您将把 PrismaService 添加到 prisma.module.ts 文件中的 exports 数组:
现在,任何导入 PrismaModule 的模块都将可以访问 PrismaService,并可以将其注入到自己的组件/服务中。这是 NestJS 应用程序的常见模式。
至此,您已完成 Prisma 的设置!现在您可以开始构建 REST API 了。
设置 Swagger
Swagger 是一个使用OpenAPI 规范来文档化您的 API 的工具。Nest 有一个专门用于 Swagger 的模块,您将很快使用它。
首先安装所需的依赖:
现在打开 main.ts 并使用 SwaggerModule 类初始化 Swagger:
应用程序运行时,打开浏览器并导航到 https://:3000/api。您应该会看到 Swagger UI。

为 Article 模型实现 CRUD 操作
在本节中,您将为 Article 模型实现创建、读取、更新和删除 (CRUD) 操作以及任何附带的业务逻辑。
生成 REST 资源
在实现 REST API 之前,您需要为 Article 模型生成 REST 资源。这可以使用 Nest CLI 快速完成。在您的终端中运行以下命令:
您会收到一些 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 页面,您应该会看到类似以下内容:

The SwaggerModule 会搜索路由处理程序上的所有 @Body()、@Query() 和 @Param() 装饰器来生成此 API 页面。
将 PrismaClient 添加到 Articles 模块
要在 Articles 模块中访问 PrismaClient,您必须将 PrismaModule 作为导入添加。将以下 imports 添加到 ArticlesModule:
现在,您可以在 ArticlesService 内部注入 PrismaService 并使用它来访问数据库。为此,请在 articles.service.ts 中添加一个构造函数,如下所示:
定义 GET /articles 端点
此端点的控制器名为 findAll。此端点将返回数据库中所有已发布的文章。findAll 控制器如下所示:
您需要更新 ArticlesService.findAll() 以返回数据库中所有已发布文章的数组:
The findMany 查询将返回所有符合 where 条件的 article 记录。
您可以通过访问 https://:3000/api 并点击 GET/articles 下拉菜单来测试该端点。点击 Try it out (尝试一下),然后点击 Execute (执行) 以查看结果。

注意:您也可以直接在浏览器中或通过 REST 客户端(例如 Postman)运行所有请求。Swagger 还会为每个请求生成 curl 命令,以防您想在终端中运行 HTTP 请求。
定义 GET /articles/drafts 端点
您将定义一个新的路由来获取所有未发布的文章。NestJS 没有自动为该端点生成控制器路由处理程序,因此您必须自己编写。
您的编辑器应该会显示一个错误,提示不存在名为 articlesService.findDrafts() 的函数。要解决此问题,请在 ArticlesService 中实现 findDrafts 方法:
The GET /articles/drafts 端点现在将在 Swagger API 页面中可用。
注意:我建议您在完成每个端点实现后,通过 Swagger API 页面对其进行测试。
定义 GET /articles/:id 端点
此端点的控制器路由处理程序名为 findOne。它看起来像这样:
该路由接受一个动态的 id 参数,该参数被传递给 findOne 控制器路由处理程序。由于 Article 模型有一个整数 id 字段,因此需要使用 + 运算符将 id 参数转换为数字。
现在,更新 ArticlesService 中的 findOne 方法,以返回具有给定 ID 的文章:
再次,通过访问 https://:3000/api 来测试该端点。点击 GET /articles/{id} 下拉菜单。点击 Try it out (尝试一下),为 id 参数添加一个有效值,然后点击 Execute (执行) 以查看结果。

定义 POST /articles 端点
这是用于创建新文章的端点。此端点的控制器路由处理程序名为 create。它看起来像这样:
注意,它期望请求正文中包含 CreateArticleDto 类型的参数。DTO(数据传输对象)是一个定义数据如何通过网络发送的对象。目前,CreateArticleDto 是一个空类。您将向其中添加属性以定义请求正文的形状。
The @ApiProperty 装饰器是必需的,以使类属性对 SwaggerModule 可见。有关此内容的更多信息可在NestJS 文档中找到。
The CreateArticleDto 现在应该在 Swagger API 页面中的 Schemas (模式) 下定义。UpdateArticleDto 的形状是根据 CreateArticleDto 的定义自动推断的。因此,UpdateArticleDto 也在 Swagger 中定义。

现在更新 ArticlesService 中的 create 方法,以在数据库中创建新文章:
定义 PATCH /articles/:id 端点
此端点用于更新现有文章。此端点的路由处理程序名为 update。它看起来像这样:
The updateArticleDto 的定义被定义为 CreateArticleDto 的PartialType。因此,它可以拥有 CreateArticleDto 的所有属性。
就像之前一样,您必须更新此操作对应的服务方法:
The article.update 操作将尝试查找具有给定 id 的 Article 记录,并使用 updateArticleDto 的数据对其进行更新。
如果在数据库中未找到此类 Article 记录,Prisma 将返回错误。在这种情况下,API 不会返回用户友好的错误消息。您将在未来的教程中学习 NestJS 的错误处理。
定义 DELETE /articles/:id 端点
此端点用于删除现有文章。此端点的路由处理程序名为 remove。它看起来像这样:
就像之前一样,前往 ArticlesService 并更新相应的方法:
这是 articles 端点的最后一个操作。恭喜,您的 API 几乎准备就绪!🎉
在 Swagger 中分组端点
为 ArticlesController 类添加 @ApiTags 装饰器,以便在 Swagger 中将所有 articles 端点分组在一起:
The API 页面现在将 articles 端点分组在一起。

更新 Swagger 响应类型
如果您查看 Swagger 中每个端点下的响应 (Responses) 选项卡,您会发现描述 (Description) 是空的。这是因为 Swagger 不知道任何端点的响应类型。您将使用一些装饰器来解决这个问题。
首先,您需要定义一个实体,Swagger 可以用它来识别返回的 entity 对象的形状。为此,请按如下方式更新 articles.entity.ts 文件中的 ArticleEntity 类:
这是 Prisma Client 生成的 Article 类型的一个实现,为每个属性添加了 @ApiProperty 装饰器。
现在,是时候使用正确的响应类型来标注控制器路由处理程序了。NestJS 为此提供了一组装饰器。
您为 GET、PATCH 和 DELETE 端点添加了 @ApiOkResponse,并为 POST 端点添加了 @ApiCreatedResponse。type 属性用于指定返回类型。您可以在NestJS 文档中找到 NestJS 提供的所有响应装饰器。
现在,Swagger 应该在 API 页面上正确定义所有端点的响应类型。

总结与最终说明
恭喜!您已经使用 NestJS 构建了一个基本的 REST API。在本教程中,您:
- 使用 NestJS 构建了 REST API
- 在 NestJS 项目中顺利集成了 Prisma
- 使用 Swagger 和 OpenAPI 文档化了您的 REST API
本教程的主要收获之一是使用 NestJS 和 Prisma 构建 REST API 是多么容易。这是一个极具生产力的技术栈,可用于快速构建结构良好、类型安全且易于维护的后端应用程序。
您可以在 GitHub 上找到此项目的源代码。如果您发现问题,请随时在仓库中提出问题或提交 PR。您也可以直接在 Twitter 上联系我。
不要错过下一篇文章!
订阅 Prisma 邮件列表