2022年6月3日

使用 NestJS 和 Prisma 构建 REST API

阅读时长 20 分钟

NestJS 是 Node.js 框架中的佼佼者,最近受到了开发者的广泛喜爱和关注。本文将教你如何使用 NestJS、Prisma、PostgreSQL 和 Swagger 构建后端 REST API。

Building a REST API with NestJS and Prisma

目录

引言

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

The final application

您将使用的技术

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

先决条件

假定知识

这是一个对初学者友好的教程。但是,本教程假定您具备以下知识:

  • JavaScript 或 TypeScript 的基础知识(推荐)
  • NestJS 的基础知识

注意:如果您不熟悉 NestJS,可以通过阅读 NestJS 文档中的概览部分快速学习基础知识。

开发环境

为了顺利完成本教程,您需要:

  • ... 已安装 Node.js
  • ... 已安装 DockerPostgreSQL
  • ... 已安装 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 http://localhost:3000/。您应该会看到一个显示消息 'Hello World!' 的空白页面。

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

创建一个 PostgreSQL 实例

您将使用 PostgreSQL 作为 NestJS 应用程序的数据库。本教程将向您展示如何通过 Docker 容器在您的机器上安装和运行 PostgreSQL。

注意:如果您不想使用 Docker,可以本地安装 PostgreSQL 实例,或者在 Heroku 上获取一个托管式 PostgreSQL 数据库

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

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

关于此配置需要了解的几点:

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

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

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

恭喜 🎉!您现在拥有自己的 PostgreSQL 数据库可以开始使用了!

注意:如果您关闭终端窗口,容器也会停止。如果您在命令末尾添加 -d 选项,例如:docker-compose up -d,则可以避免这种情况。这将使容器在后台无限期运行。

设置 Prisma

现在数据库已准备就绪,是时候设置 Prisma 了!

初始化 Prisma

首先,将 Prisma CLI 作为开发依赖安装。Prisma CLI 允许您运行各种命令并与项目进行交互。

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

这将在您的项目中创建一个新的 prisma 目录,其中包含一个 schema.prisma 文件。这是包含数据库 Schema 的主要配置文件。此命令还会在您的项目中创建一个 .env 文件。

设置环境变量

.env 文件中,您应该会看到一个名为 DATABASE_URL 的环境变量,其中包含一个虚拟连接字符串。将其替换为您 PostgreSQL 实例的连接字符串。

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

理解 Prisma Schema

如果您打开 prisma/schema.prisma,您应该会看到以下默认的 Schema:

此文件采用 Prisma Schema Language 编写,Prisma 使用此语言定义数据库 Schema。schema.prisma 文件包含三个主要组成部分:

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

注意:有关 Prisma Schema 的更多信息,请查阅Prisma 文档

建模数据

现在是时候为您的应用程序定义数据模型了。在本教程中,您只需要一个 Article 模型来表示博客中的每篇文章。

prisma/prisma.schema 文件中,向您的 Schema 添加一个名为 Article 的新模型:

在这里,您创建了一个包含多个字段的 Article 模型。每个字段都有一个名称(idtitle 等)、一个类型(IntString 等)以及其他可选属性(@id@unique 等)。通过在字段类型后添加 ? 可以使字段变为可选。

字段 id 有一个名为 @id 的特殊属性。此属性表明该字段是模型的主键。@default(autoincrement()) 属性表明该字段应该自动递增并分配给任何新创建的记录。

字段 published 是一个标志,用于指示文章是已发布还是处于草稿模式。@default(false) 属性表明此字段默认为 false

两个 DateTime 字段,createdAtupdatedAt,将跟踪文章的创建时间和上次更新时间。@updatedAt 属性将在文章修改时自动使用当前时间戳更新字段。

迁移数据库

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

此命令将执行三件事:

  1. 保存迁移: Prisma Migrate 将对您的 Schema 进行快照,并找出执行迁移所需的 SQL 命令。Prisma 会将包含 SQL 命令的迁移文件保存到新创建的 prisma/migrations 文件夹中。
  2. 执行迁移: Prisma Migrate 将执行迁移文件中的 SQL,在您的数据库中创建底层表。
  3. 生成 Prisma Client: Prisma 将根据您的最新 Schema 生成 Prisma Client。由于您尚未安装 Client 库,CLI 也会为您安装它。您应该会在 package.json 文件的 dependencies 中看到 @prisma/client 包。Prisma Client 是根据您的 Prisma Schema 自动生成的 TypeScript 查询构建器。它为您的 Prisma Schema 量身定制,将用于向数据库发送查询。

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

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

检查生成的迁移文件,了解 Prisma Migrate 在幕后做了什么:

注意:您的迁移文件名会略有不同。

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

填充数据库(Seed)

目前,数据库是空的。因此,您将创建一个 填充脚本(seed script),用一些虚拟数据填充数据库。

首先,创建一个名为 prisma/seed.ts 的填充文件。此文件将包含填充数据库所需的虚拟数据和查询。

然后,在填充文件内部,添加以下代码:

在此脚本中,您首先初始化 Prisma Client。然后使用 prisma.upsert() 函数创建两篇文章。upsert 函数仅在没有文章匹配 where 条件时才会创建新文章。您使用 upsert 查询而不是 create 查询,因为 upsert 可以避免意外尝试插入同一记录两次相关的错误。

您需要告诉 Prisma 在运行填充命令时执行哪个脚本。您可以通过将 prisma.seed 键添加到 package.json 文件末尾来完成此操作:

seed 命令将执行您之前定义的 prisma/seed.ts 脚本。此命令应该会自动工作,因为 ts-node 已作为开发依赖安装在您的 package.json 中。

使用以下命令执行填充:

您应该会看到以下输出:

注意:您可以在Prisma 文档中了解有关填充(seeding)的更多信息。

创建一个 Prisma 服务

在您的 NestJS 应用程序中,将 Prisma Client API 从应用程序中抽象出来是一个很好的实践。为此,您将创建一个新的服务来包含 Prisma Client。此服务称为 PrismaService,将负责实例化 PrismaClient 实例并连接到数据库。

Nest CLI 提供了直接从 CLI 生成模块和服务的便捷方式。在终端中运行以下命令:

注意 1:如有必要,请参阅 NestJS 文档中关于服务模块的介绍。

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

这将生成一个新的子目录 ./src/prisma,其中包含 prisma.module.tsprisma.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:

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

Swagger User Interface

实现 Article 模型的 CRUD 操作

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

生成 REST 资源

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

系统会给出一些 CLI 提示。请相应地回答问题:

  1. 您想为此资源使用什么名称(复数,例如“users”)? articles
  2. 您使用哪种传输层? REST API
  3. 您想生成 CRUD 入口点吗?

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

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

Auto-generated "articles" endpoints

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

PrismaClient 添加到 Articles 模块

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

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

定义 GET /articles 端点

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

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

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

您可以通过访问 http://localhost:3000/api 并点击 GET/articles 下拉菜单来测试此端点。按下 尝试一下,然后按下 执行 以查看结果。

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

定义 GET /articles/drafts 端点

你将定义一个新的路由来获取所有 未发布的 文章。NestJS 没有为此端点自动生成 Controller 路由处理程序,因此你需要自己编写。

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

GET /articles/drafts 端点现在将在 Swagger API 页面中可用。

注意:我建议你在完成实现后,通过 Swagger API 页面测试每个端点。

定义 GET /articles/:id 端点

这个端点的 Controller 路由处理程序名为 findOne。它看起来像这样

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

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

再次通过访问 http://localhost:3000/api 来测试该端点。点击 GET /articles/{id} 下拉菜单。按下 Try it out,为 id 参数添加一个有效值,然后按下 Execute 查看结果。

定义 POST /articles 端点

这是用于创建新文章的端点。此端点的 Controller 路由处理程序称为 create。它看起来像这样

请注意,它期望请求正文中的参数类型为 CreateArticleDto。DTO(数据传输对象)是一个定义数据如何在网络上传输的对象。目前,CreateArticleDto 是一个空类。你将向其中添加属性来定义请求正文的形状。

需要 @ApiProperty 装饰器才能使类属性对 SwaggerModule 可见。有关更多信息,请参阅 NestJS 文档

CreateArticleDto 现在应该在 Swagger API 页面的 Schemas 下定义。UpdateArticleDto 的形状是根据 CreateArticleDto 的定义自动推断的。因此 UpdateArticleDto 也在 Swagger 内部定义。

现在更新 ArticlesService 中的 create 方法,以在数据库中创建新文章。

定义 PATCH /articles/:id 端点

此端点用于更新现有文章。此端点的路由处理程序称为 update。它看起来像这样

updateArticleDto 定义被定义为 CreateArticleDtoPartialType。因此它可以拥有 CreateArticleDto 的所有属性。

和之前一样,你必须更新此操作对应的 Service 方法。

article.update 操作将尝试查找具有给定 idArticle 记录,并使用 updateArticleDto 的数据对其进行更新。

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

定义 DELETE /articles/:id 端点

此端点用于删除现有文章。此端点的路由处理程序称为 remove。它看起来像这样

和之前一样,前往 ArticlesService 并更新对应的方法。

这是 articles 端点的最后一次操作。恭喜你,你的 API 快准备好了!🎉

在 Swagger 中将端点分组

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

API 页面现在将 articles 端点分组在一起了。

更新 Swagger 响应类型

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

首先,你需要定义一个实体,Swagger 可以用它来识别返回的 entity 对象的形状。为此,请按如下方式更新 articles.entity.ts 文件中的 ArticleEntity 类:

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

现在,是时候用正确的响应类型来注解 Controller 路由处理程序了。NestJS 为此提供了一组装饰器。

你为 GETPATCHDELETE 端点添加了 @ApiOkResponse,为 POST 端点添加了 @ApiCreatedResponsetype 属性用于指定返回类型。你可以在 NestJS 文档中找到 NestJS 提供的所有响应装饰器。

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

总结与最终说明

恭喜!你使用 NestJS 构建了一个基本的 REST API。在本教程中,你

  • 使用 NestJS 构建了 REST API
  • 将 Prisma 平滑地集成到 NestJS 项目中
  • 使用 Swagger 和 OpenAPI 文档化了你的 REST API

本教程的主要收获之一是使用 NestJS 和 Prisma 构建 REST API 是多么容易。这是一个极具生产力的技术栈,用于快速构建结构良好、类型安全且易于维护的后端应用程序。

你可以在 GitHub 上找到此项目的源代码。如果你发现问题,请随时在仓库中提出 issue 或提交 PR。你也可以直接在 Twitter 上联系我。

不要错过下一篇文章!

订阅 Prisma 新闻通讯