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 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
容器的端口5432
。5432
是 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
模型。每个字段都有一个名称(id
、title
等)、一个类型(Int
、String
等)以及其他可选属性(@id
、@unique
等)。通过在字段类型后添加 ?
可以使字段变为可选。
字段 id
有一个名为 @id
的特殊属性。此属性表明该字段是模型的主键。@default(autoincrement())
属性表明该字段应该自动递增并分配给任何新创建的记录。
字段 published
是一个标志,用于指示文章是已发布还是处于草稿模式。@default(false)
属性表明此字段默认为 false
。
两个 DateTime
字段,createdAt
和 updatedAt
,将跟踪文章的创建时间和上次更新时间。@updatedAt
属性将在文章修改时自动使用当前时间戳更新字段。
迁移数据库
定义了 Prisma Schema 后,您将运行迁移以在数据库中创建实际的表。要生成并执行您的第一次迁移,请在终端中运行以下命令:
此命令将执行三件事:
- 保存迁移: 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 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 生成模块和服务的便捷方式。在终端中运行以下命令:
注意 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:
应用程序运行时,打开浏览器并导航到 http://localhost:3000/api
。您应该会看到 Swagger UI。
实现 Article
模型的 CRUD 操作
在本节中,您将实现 Article
模型的创建(Create)、读取(Read)、更新(Update)和删除(Delete)(CRUD)操作以及任何伴随的业务逻辑。
生成 REST 资源
在实现 REST API 之前,您需要为 Article
模型生成 REST 资源。这可以使用 Nest CLI 快速完成。在终端中运行以下命令:
系统会给出一些 CLI 提示。请相应地回答问题:
您想为此资源使用什么名称(复数,例如“users”)?
articles您使用哪种传输层?
REST API您想生成 CRUD 入口点吗?
是
现在您应该会找到一个新的 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
作为导入项添加。将以下 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
定义被定义为 CreateArticleDto
的 PartialType
。因此它可以拥有 CreateArticleDto
的所有属性。
和之前一样,你必须更新此操作对应的 Service 方法。
article.update
操作将尝试查找具有给定 id
的 Article
记录,并使用 updateArticleDto
的数据对其进行更新。
如果在数据库中找不到此类 Article
记录,Prisma 将返回一个错误。在这种情况下,API 不会返回用户友好的错误消息。你将在未来的教程中学习如何在 NestJS 中处理错误。
定义 DELETE /articles/:id
端点
此端点用于删除现有文章。此端点的路由处理程序称为 remove
。它看起来像这样
和之前一样,前往 ArticlesService
并更新对应的方法。
这是 articles
端点的最后一次操作。恭喜你,你的 API 快准备好了!🎉
在 Swagger 中将端点分组
在 ArticlesController
类中添加 @ApiTags
装饰器,以便在 Swagger 中将所有 articles
端点分组在一起。
更新 Swagger 响应类型
如果你在 Swagger 中查看每个端点下的 Responses 选项卡,你会发现 Description 是空的。这是因为 Swagger 不知道任何端点的响应类型。你将使用一些装饰器来解决这个问题。
首先,你需要定义一个实体,Swagger 可以用它来识别返回的 entity
对象的形状。为此,请按如下方式更新 articles.entity.ts
文件中的 ArticleEntity
类:
这是 Prisma Client 生成的 Article
类型的一个实现,并在每个属性上添加了 @ApiProperty
装饰器。
现在,是时候用正确的响应类型来注解 Controller 路由处理程序了。NestJS 为此提供了一组装饰器。
你为 GET
、PATCH
和 DELETE
端点添加了 @ApiOkResponse
,为 POST
端点添加了 @ApiCreatedResponse
。type
属性用于指定返回类型。你可以在 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 新闻通讯