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 容器的规范。在文件中创建以下配置:
关于此配置,有几点需要理解:
- 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:
应用程序运行时,打开浏览器并导航到 http://localhost: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
记录。
您可以通过访问 http://localhost: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 的文章:
再次,通过访问 http://localhost: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 邮件列表