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://127.0.0.1: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 是一个 TypeScript 查询构建器,它从您的 Prisma 模式自动生成。它针对您的 Prisma 模式进行了定制,并将用于向数据库发送查询。

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

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

查看生成的迁移文件,以了解 Prisma Migrate 在幕后所做的事情

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

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

填充数据库

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

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

然后,在种子文件中,添加以下代码

在此脚本中,您首先初始化 Prisma Client。然后,您使用 prisma.upsert() 函数创建两篇文章。如果没有任何文章与 where 条件匹配,则 upsert 函数将仅创建一篇新文章。您正在使用 upsert 查询而不是 create 查询,因为 upsert 消除了与意外尝试插入同一记录两次相关的错误。

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

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

使用以下命令执行填充

您应该看到以下输出

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

创建 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单例实例,并允许在您的整个应用程序中共享该服务。为此,您将在 prisma.module.ts 文件中将 PrismaService 添加到 exports 数组

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

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

设置 Swagger

Swagger 是一种使用 OpenAPI 规范来记录您的 API 的工具。 Nest 有一个用于 Swagger 的专用模块,您将很快使用它。

首先安装所需的依赖项

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

在应用程序运行时,打开您的浏览器并导航到 https://127.0.0.1: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 入口点吗? Yes

您现在应该找到一个新的 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 记录。

您可以通过转到 https://127.0.0.1:3000/api 并单击 GET/articles 下拉菜单来测试端点。按 Try it out,然后按 Execute 以查看结果。

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

定义 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://127.0.0.1:3000/api 测试该端点。点击 GET /articles/{id} 下拉菜单。按 试用,在 id 参数中添加一个有效值,然后按 执行 以查看结果。

定义 POST /articles 端点

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

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

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

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

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

定义 PATCH /articles/:id 端点

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

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

就像之前一样,您必须更新此操作的相应服务方法

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

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

定义 DELETE /articles/:id 端点

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

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

那是 articles 端点的最后一个操作。恭喜!你的 API 几乎准备就绪了!🎉

在 Swagger 中将端点分组在一起

@ApiTags 装饰器添加到 ArticlesController 类,以在 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
  • 在 NestJS 项目中顺利集成了 Prisma
  • 使用 Swagger 和 OpenAPI 文档化了你的 REST API

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

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

不要错过下一篇文章!

注册 Prisma 新闻邮件