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 添加了一些非常好的 IntelliSense 和语法高亮功能。

注意 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 容器的规范。在文件中创建以下配置

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

  • 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 文件。这是包含数据库模式的主要配置文件。此命令还在您的项目中创建了一个 .env 文件。

设置您的环境变量

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

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

理解 Prisma 模式

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

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

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

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

建模数据

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

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

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

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

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

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

迁移数据库

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

此命令将执行三件事

  1. 保存迁移:Prisma Migrate 将对您的模式进行快照,并找出执行迁移所需的 SQL 命令。Prisma 会将包含 SQL 命令的迁移文件保存到新创建的 prisma/migrations 文件夹中。
  2. 执行迁移:Prisma Migrate 将执行迁移文件中的 SQL 以在数据库中创建基础表。
  3. 生成 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 在运行 seeding 命令时要执行哪个脚本。您可以通过将 prisma.seed 键添加到 package.json 文件的末尾来完成此操作

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

使用以下命令执行 seeding

您应该会看到以下输出

注意:您可以在 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

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

Swagger User Interface

实现 Article 模型的 CRUD 操作

在本节中,您将为 Article 模型及其伴随的业务逻辑实现创建、读取、更新和删除 (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

您现在可以将 PrismaService 注入到 ArticlesService 中,并使用它来访问数据库。为此,请像这样向 articles.service.ts 添加一个构造函数

定义 GET /articles 端点

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

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

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 方法

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 是一个空类。您将向其中添加属性以定义请求正文的形状。

@ApiProperty 装饰器是使类属性对 SwaggerModule 可见所必需的。有关此内容的更多信息,请参阅 NestJS 文档

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

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

定义 PATCH /articles/:id 端点

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

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

像以前一样,您必须更新此操作对应的服务方法

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 装饰器。

现在,是时候用正确的响应类型注解控制器路由处理程序了。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 上找到此项目的源代码。如果您发现问题,请随时在仓库中提出问题或提交 PR。您也可以直接在 Twitter 上联系我。

不要错过下一篇文章!

订阅 Prisma 新闻通讯

© . This site is unofficial and not affiliated with Prisma Data, Inc.