作为一名 Node.js 后端开发人员,您会同意,默认情况下,Node.js 非常简单,并且在构建应用程序时不会假设您需要什么。因此,您负责设置要在应用程序中使用的所有内容,包括处理路由、进行 API 调用、设置 TypeScript 或 Web 套接字,甚至是代码组织、文件结构和命名约定等基本内容。
管理大型应用程序可能是一项艰巨的任务,特别是如果它的设计没有清晰的结构和严格的代码组织准则。
Nest.js 尝试通过围绕 Node.js 创建抽象来解决其中一些问题,以便开发人员可以专注于应用程序问题,而不是其他微小的实现细节。
在本指南中,您将从上到下学习 Nest.js 的核心基础知识,旨在让您加快速度,以便您可以在 Nest.js 的帮助下立即构建企业级 Node.js 应用程序。
我们将通过本指南学到的一切都是渐进的;涵盖了介绍性概念的大量内容。为了充分利用本指南,编写代码会有所帮助。
伙计们,让我们开始吧!
源代码: 像往常一样,您可以分叉和修补托管在 GitHub上.
请注意: 我们将使用 Postman 来测试我们演示中的 API。 您可以在 邮递员下载页面. 或者,您可以简单地使用浏览器、命令行 curl
工具,或您可能熟悉的任何其他工具。
什么是 Nest.js
将 Nest.js 视为 Node.js 的超集,它抽象出困难的任务、工具和样板代码,同时还为使用现代 JavaScript 和 TypeScript 的应用程序开发添加了成熟的工具包。
Nest.js 提供了一个开箱即用的应用程序架构,允许开发人员和团队通过利用社区中现成的、突出的选项和模块(例如Express.js 应用程序。您甚至可以将 Express(默认情况下在后台使用)替换为 Fastify,但这样做意味着您可能需要在应用程序中使用不同的 Fastify 兼容库。
它结合了函数式编程、面向对象编程和函数式响应式编程的特点,拥有超过 52.4 个 star 和 6.2 个 fork GitHub上 每周下载量高达 1,784,004,先进的 Node.js 框架是构建高效、可扩展的企业级服务器端应用程序的热门选择。
Nest.js 的特点
以下是 Nest.js 发展成为如此流行的 Node.js 框架的原因:
- Nest.js 的创建是为了帮助开发人员构建单体应用程序和微服务。
- 虽然它功能强大,但也适合开发人员使用;易于使用、快速学习且易于应用。
- 它开箱即用地利用 TypeScript(JavaScript 的超集),为开发人员编写没有运行时错误的可维护代码提供了空间。
- 它拥有命令行界面,有助于提高开发人员的工作效率并简化开发。
- 使用 Nest.js 构建时,无论您是引导最小可行产品还是开发应用程序,开发流程都会得到增强并节省时间,因为 Nest 默认情况下具有令人惊叹的项目文件夹结构。
- 它支持各种特定于 Nest 的模块,有助于集成常见概念和技术,包括 TypeORM、GraphQL、日志记录、验证、Mongoose、WebSockets、缓存等。
- Nest.js 可以自豪地拥有任何框架的一些最佳文档。它的文档详尽、易于理解,有助于节省调试时间,因为当需要解决问题时,它可以轻松完成。
- Nest.js 与 Jest 集成,这使得在应用程序上编写单元测试变得简单。
- 它是为小型和大型企业应用程序而构建的。
创建 Nest.js 项目
要在本地计算机上开始使用 Nest.js,您首先必须安装 Nest 命令行界面 (CLI),这将有助于构建新的 Nest.js 项目文件夹,并使用构建所需的核心文件和模块填充该文件夹。 Nest.js 应用程序。
运行以下命令来安装 Nest.js 命令行界面:
$ npm i -g @nestjs/cli
// Or
$ yarn global add @nestjs/cli
// Or
$ pnpm add -g @nestjs/cli
在本地计算机上成功全局安装 Nest.js CLI 后,您可以运行 nest
在命令行上查看我们可以使用的各种命令:
$ nest
结果是:
Usage: nest [options]
Options:
-v, --version Output the current version.
-h, --help Output usage information.
Commands:
new|n [options] [name] Generate Nest application.
build [options] [app] Build Nest application.
start [options] [app] Run Nest application.
info|i Display Nest project details.
add [options] Adds support for an external library to your project.
generate|g [options] [name] [path] Generate a Nest element.
Schematics available on @nestjs/schematics collection:
┌───────────────┬─────────────┬──────────────────────────────────────────────┐
│ name │ alias │ description │
│ application │ application │ Generate a new application workspace │
│ class │ cl │ Generate a new class │
│ configuration │ config │ Generate a CLI configuration file │
│ controller │ co │ Generate a controller declaration │
│ decorator │ d │ Generate a custom decorator │
│ filter │ f │ Generate a filter declaration │
│ gateway │ ga │ Generate a gateway declaration │
│ guard │ gu │ Generate a guard declaration │
│ interceptor │ itc │ Generate an interceptor declaration │
│ interface │ itf │ Generate an interface │
│ middleware │ mi │ Generate a middleware declaration │
│ module │ mo │ Generate a module declaration │
│ pipe │ pi │ Generate a pipe declaration │
│ provider │ pr │ Generate a provider declaration │
│ resolver │ r │ Generate a GraphQL resolver declaration │
│ service │ s │ Generate a service declaration │
│ library │ lib │ Generate a new library within a monorepo │
│ sub-app │ app │ Generate a new application within a monorepo │
│ resource │ res │ Generate a new CRUD resource │
└───────────────┴─────────────┴──────────────────────────────────────────────┘
在这里,您将看到如何使用这些命令,现在可以点击 new|n [options] [name]
命令来创建您的第一个 Nest.js 项目:
$ nest new getting-started-with-nestjs
// Or
$ nest n getting-started-with-nestjs
接下来,系统会询问您想要使用什么包管理器:
? Which package manager would you ❤️ to use? (Use arrow keys)
npm
yarn
> pnpm
请随意选择您喜欢的包管理器,我会选择 pnpm
。这是因为它的效率和速度比 NPM 大约高出三倍,并且凭借快速的缓存系统,PNPM 也比 Yarn 更快。
选择包管理器后,安装过程将继续,然后将创建 Nest.js 应用程序。
现在你可以 cd
进入新创建的项目,然后使用您选择的编辑器打开它:
$ cd getting-started-with-nestjs
现在创建了项目,我们可以使用以下命令之一运行它:
$ npm run start
// Or
$ yarn start
// Or
$ pnpm run start
如果你看看 package.json
文件中,您会注意到脚本段中的值 pnpm run start
is nest start
:
"start": "nest start",
这意味着您还可以通过运行以下命令来运行 Nest.js 应用程序:
$ nest start
Nest.js 项目结构概览
让我们仔细看看 Nest 应用的结构:
/package.json
package.json
文件是 Node.js 以及 Nest.js 项目的核心。它保存有关项目的所有元数据,并定义安装应用程序依赖项或运行项目脚本所需的项目的各种功能属性。
我们已经看到了 start
脚本。
start:dev
profile 可以监视应用程序中的更改并自动重新加载它,而无需停止应用程序并重新启动它 - 它适用于开发。这 start:prod
当您想要测试您的应用程序是否已做好生产准备以及将其部署到生产环境时,该脚本以及用于测试 Nest.js 应用程序的其他脚本非常有用。
@nestjs/platform-express
将express定义为Nest应用程序中的默认HTTP服务器。
/tsconfig.json
tsconfig.json
file 是一个用 JSON(JavaScript 对象表示法)编写的文件,定义了编译 Nest 应用程序所需的 TypeScript 相关选项。
/nest-cli.json
它保存构建、组织或部署 Nest 应用程序所需的元数据。
/test
该目录包含运行 Nest 测试所需的所有文件。 Nest 使用 Jest 框架通过 Jest 配置进行测试 jest-e2e.json
文件中。
/src
src
目录是 Nest 项目核心的父文件夹。它持有 main.ts
file 这是 Nest 应用程序启动的文件。的工作 main.ts
文件要加载 AppModule
是从进口的 /src/app.module.ts
.
在本指南的后面部分,我们将了解模块; Nest.js 应用程序的主要组件之一。
AppModule
是一个作为模块创建的类,使用 @Module
装饰师。在里面 app.module.ts
文件, AppService
止 ./app.service
和 AppController
止 ./app.controller
也是进口的。
AppController
也是一个使用创建的类 @Controller
装饰器,同时 AppService
是一个使用创建的类 @Injectable
注解。
Nest 很酷的一点是,它内部只有很少的装饰器来向任何类添加元数据,并且元数据定义了该类的用途,例如:
@Controller()
将类转换为控制器。@Module()
将类转换为模块。@Injectable()
将一个类转变为一个提供者。
另外,在 src
目录是 app.controller.spec.ts
文件,这是控制器的测试文件。
我们可以使用以下命令运行应用程序 nest start
.
该应用程序开始于 http://localhost:3000
在您的浏览器上:
我们可以更改显示的内容 http://localhost:3000
,通过前往 app.service.ts
文件,其中定义了索引路由的提供者。
Nest.js 应用程序的构建块
Nest.js 应用程序由三个主要组件组成:
- 模块
- 控制器
- 提供服务者
在了解 Nest 应用程序的构建块时,让我们首先清理 Nest 项目,方法是删除 app.controller.spec.ts
, ./app.service
, app.module.ts
及 ./app.controller
文件;只留下 main.ts
,模拟从头开始的开发生命周期。
此时,当我们删除导入的 AppModule
来自的文件 main.ts
,系统提示我们未提供“模块”参数。
为了演示 Nest 应用程序的构建块,我们将通过构建 REST API 来处理对象上的 CRUD 操作来了解一个简单的用户配置文件实现。
模块
在 src
文件夹新建一个 app.module.ts
文件,然后创建一个 AppModule
类,我们导出的。
接下来,导入 AppModule
类进入 main.ts
,然后运行 nest start
.
导航 http://localhost:3000
在您的浏览器中,您将收到 404 错误:
这是因为我们尚未为 Nest 应用的基本 URL 定义路由。
早在 app.module.ts
,我们有 AppModule
我们拥有的类还不是 Nest 模块。为了使其成为 Nest 模块,我们添加 @Module()
装饰器是从进口的 @nestjs/common
然后我们传递一个空对象。
import { Module } from '@nestjs/common';
@Module({})
export class AppModule {}
现在,我们有了一个 Nest.js 模块!
请注意: 模块是一个带有注释的类 @Module()
装饰器。
每个 Nest 应用程序都有一个根模块,用作解析 Nest 应用程序的结构和关系的入口点。
强烈建议使用多个模块来组织应用程序的组件。
@Module()
装饰器使开发人员可以在 Nest 应用中定义有关类的元数据。
当有多个模块的情况下,例如用户模块、订单模块、聊天模块等, app.module.ts
应用于注册 Nest 应用程序的所有其他模块。
创建路线;控制器
在 Nest 应用程序中创建路由需要控制器。控制器的目的是接收对 Nest 应用程序的特定请求;控制应用程序内各种路由的请求和响应周期。
当客户端向 Nest 应用程序发出 HTTP 请求时,与发出请求的路由相匹配的路由会处理该请求并返回适当的响应。
要在 Nest 应用中创建控制器,我们必须使用 @Controller()
装饰器。
在 src
目录下,新建一个文件 app.contoller.ts
,其中,我们可以定义一个 Nest 控制器:
import { Controller } from '@nestjs/common';
@Controller({})
export class AppController {}
这就对了!我们有一个非常好的控制器,但要创建新路线,我们需要首先让 Nest 应用程序了解创建的控制器。
为了实现这一目标,我们确保导入 AppController
在app.module.ts中,并定义有关控制器的信息 @Module()
装饰器 - 作为控制器数组:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
controllers: [AppController],
})
export class AppModule {}
处理 GET 请求
然后我们定义一个简单的 getUser()
路线(与 @Get()
用于处理对指定路径的 HTTP GET 请求的装饰器)作为基本路由,我们可以在浏览器上访问相同的 https://localhost:3000
:
import { Controller, Get } from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
getUser() {
return 'I am a great person';
}
}
结果是:
嗯,这里我们只返回一个字符串,但是如果我们想返回一个对象怎么办?我们可以定义一个对象来代替字符串:
import { Controller, Get } from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
getUser() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
}
}
导航 http://localhost:3000
在浏览器中,您将看到该对象:
远离基本路线,创建一条类似于以下的路线怎么样? http://localhost:3000/user
用于获取所有用户?
我们可以创建一个控制器来通过几种方式处理这样的路由。
一种方法是定义一种新方法,使用 @Get()
装饰器/处理程序。
import { Controller, Get } from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
getUser() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
}
}
Nest.js 为所有各种 HTTP 方法提供装饰器或处理程序,包括 @Get()
, @Post()
, @Put()
, @Delete()
, @Patch()
, @Options()
及 @Head()
.
@All()
装饰器定义了一个处理所有各种方法的端点。
处理 POST 请求
我们还可以定义 POST 请求以将数据存储在数据库中,使用 @Post()
装饰器:
import { Controller, Post } from '@nestjs/common';
@Controller({})
export class AppController {
@Post()
store() {
return 'Post request successful';
}
}
然后,我们使用 Postman 测试 POST 请求,并注意到字符串按照定义成功返回。
您可能会问,如果我还想做的不仅仅是返回数据怎么办?也许,是为了发送数据。
为此,您需要将数据注入到路由方法中,如下所示:
import { Controller, Post, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller({})
export class AppController {
@Post()
store(@Req() req: Request) {
return req.body;
}
}
现在,当我们使用 Postman 测试 POST 请求时,我们可以查看正在发送的数据。在这种情况下,它只是一个空对象:
查看我们的 Git 学习实践指南,其中包含最佳实践、行业认可的标准以及随附的备忘单。 停止谷歌搜索 Git 命令,实际上 学习 它!
带路由参数的动态路由
假设您想要接受动态数据作为请求的一部分。首先,我们需要在路由的路径中定义 token,以便记录路由/URL 上的动态位置,然后使用 @Param()
装饰器,可以像这样访问路由参数:
import { Controller, Get, Param } from '@nestjs/common';
@Controller({})
export class AppController {
@Get('/:userId')
getUser(@Param() userId: number) {
return userId;
}
}
userId
成功返回:
处理异步请求
Nest.js 能够使用各种方法处理返回 Promise 的异步请求:
import { Controller, Get} from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
async findAll(): Promise {
return [];
}
}
在上面的方法中,异步性是使用 async
关键词。另一种方法是返回 RxJS 可观察流:
import { Controller, Get} from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
findAll(): Observable {
return of([]);
}
}
在这里,Nest.js 将在后台订阅源,当流完成时,它将自动获取最后发出的值。
在 Nest 中处理重定向
@Redirect()
装饰器用于将响应重定向到不同的 URL。这 @Redirect()
装饰器接受两个参数——要重定向到的 URL 和重定向时的状态代码,这两个参数都是可选的:
import { Controller, Get} from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
@Redirect('https://www.ucheazubuko.com', 302)
getSite() {
return { url: 'https://stackabuse.com' };
}
}
返回状态码
要返回 Nest.js 服务器上处理的任何请求的状态代码, @HttpCode(…)
很容易就通过了。
在Nest中,GET请求的默认状态码是200,POST请求是201,错误请求是304
服务器请求的状态代码可以定义如下:
import { Controller, Post, HttpCode } from '@nestjs/common';
@Controller({})
export class AppController {
@Post()
@HttpCode(204)
create() {
return 'This action adds a new user to the app.';
}
}
处理 DELETE 请求
与发出 POST 请求类似,删除请求可以这样处理:
import { Controller, Delete, Param } from '@nestjs/common';
@Controller({})
export class AppController {
@Delete('/:userId')
delete(@Param() params: { userId: number }) {
return params;
}
}
处理更新请求
可以使用以下方法处理更新服务器上特定数据的请求 @Patch()
装饰器:
import { Controller, Patch, Req} from '@nestjs/common';
import { Request } from 'express';
@Controller({})
export class AppController {
@Patch('/:userId')
update(@Req() req: Request) {
return req.body;
}
}
现在我们已经看到了定义在强大的服务器上经常使用的典型控制器的各种方法,重要的是要注意控制器应该是精简的,干净的,并且根据用例进行定义,这样如果有另一个控制器来定义 user
路由,那么应该创建一个单独的目录并专门用于处理相同的内容 - 远离 AppController
.
然后进去 user.controller.ts
,我们可以将其中的所有路由处理程序配置为前缀 /user/
通过编写如下所示的代码:
import { Controller, Get } from '@nestjs/common';
@Controller('/user')
export class UserController {
@Get()
getUser() {
return 'I am from the user controller';
}
}
接下来,注册 UserController
在控制器的数组中 app.modules.ts
:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { UserController } from './controllers/user/user.controller';
@Module({
controllers: [AppController, UserController],
})
export class AppModule {}
当我们导航到 https:localhost:3000/user
,成功返回:
为了使项目文件夹比现在更整洁,我们可以定义一个 user.module.ts
我们将在其中定义的文件 UserController
:
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
@Module({
controllers: [UserController],
})
export class UserModule {}
然后,导入 UserModule
成 app.module.ts
:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { UserModule } from './user/user.module';
@Module({
controllers: [AppController],
imports: [UserModule],
})
export class AppModule {}
有了这个,我们就能达到和之前一样的效果了。
请注意: Nest 可以使用以下方式轻松(g)生成(mo)dules 和(co)ntrollers nest g mo
和 nest g co
命令。特定模块,例如 user
还可以使用 Nest CLI 运行以下命令来快速创建模块和控制器: nest g mo user
– 创建用户模块,以及 nest g co user
– 创建用户控制器。
提供服务者
所有从数据库获取数据的操作都应由提供者而不是控制器来处理,以在面向用户的代码和与潜在敏感数据交互的代码之间创建一个抽象层。在这些层之间 - 可以设置验证以确保正确的数据库处理。使用 Nest CLI,我们可以通过生成服务来创建提供者:
$ nest g s user
这创造了一个 UserService
其中我们将定义所有业务逻辑 UserController
,让 UserController
仅处理请求和响应。在 user.service.ts
,我们看到 @Injectable()
装饰器用于定义类。在 Nest 中,使用 @Injectable()
装饰器是将服务、存储库或帮助器类转换为提供者。
提供者通过类的构造函数注入到类中。让我们仔细看一个例子。
早些时候,在 user.controller.ts
,我们已经定义了获取用户对象的业务逻辑,但是现在,我们应该在 UserService
:
import { Controller, Injectable } from '@nestjs/common';
@Controller({})
export class AppController {
@Injectable()
get() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria'; };
}
}
接下来,在 user.controller.ts
文件中,让我们定义一个构造函数 UserController
班级。在这个构造函数中,我们提供了一个私有的 userService
,这是一种类型 UserService
班级。正是有了这个私有属性,我们才能够利用我们之前定义的用于获取用户的业务逻辑:
import { Controller, Get } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('/user')
export class UserController {
constructor(private userService: UserService) {}
@Get()
getUser() {
return this.userService.get();
}
}
因此,该 UserController
类,现在取决于 UserService
类在一个称为
依赖注入。
同样,两者的逻辑 user.controller.ts
和 user.service.ts
文件相应更新:
import {
Controller,
Delete,
Get,
Param,
Patch,
Post,
Req,
} from '@nestjs/common';
import { Request } from 'express';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Get()
getUsers() {
return this.userService.get();
}
@Get('/:userId')
getUser(@Param() param: { userId: number }) {
return this.userService.getUser(param);
}
@Post()
store(@Req() req: Request) {
return this.userService.create(req);
}
@Patch('/:userId')
update(@Req() req: Request, @Param() param: { userId: number }) {
return this.userService.update(req, param);
}
@Delete()
delete(@Param() param: { userId: number }) {
return this.userService.delete(param);
}
}
import { Injectable } from '@nestjs/common';
import { Request } from 'express';
@Injectable()
export class UserService {
get() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
}
getUser(param: { userId: number }) {
return param;
}
create(req: Request) {
return req.body;
}
update(req: Request, param: { userId: number }) {
return { body: req.body, param };
}
delete(param: { userId: number }) {
return param;
}
}
现在,让我们使用 Postman 验证端点是否正常工作。
揭秘 Nest.js 中的依赖注入
当构建应用程序的较小组件(例如类或模块)时,您的类可能依赖于另一个类或模块的功能,例如,需要利用不同类提供的 HTTP 服务来进行 API 调用,或者与持久层交互的服务层。
可以通过以下方式在控制器内提供依赖关系 依赖注入.
依赖注入是一种编程概念和模式,它表达了如何将应用程序的某些部分交付到需要它们的应用程序的其他部分,从而提供高内聚但松散的耦合。
Nest 支持依赖注入,您可以在 Nest 应用程序中使用它来增强项目的模块化性。
一个实际的例子是这样描述的:
假设A类使用了B类的一些功能,那么就说A类依赖于B类。因此,为了在A类中使用B类,我们需要先创建一个B类的实例(即创建一个B 类对象):
const b = new B ()
.
将创建类实例的任务转移到另一个类并直接使用所提供的类(注入器组件)中的依赖项称为依赖项注入。
建议: 依赖注入(DI)是 Spring Boot、Nest.js 和 Angular.js 等框架的基本概念之一,如果您想了解更多相关信息,可以查看 官方 Angular 文档.
通常,类应该只专注于实现其功能,而不是用于创建它可能需要或不需要的各种对象。
依赖注入的好处。
- 它有助于单元测试。
- 通过依赖项注入,可以减少样板代码,因为依赖项的初始化是由注入器组件完成的。
- 扩展应用程序的过程变得更加容易。
- 依赖注入有助于实现松散耦合。
探索请求负载
请记住,在各种请求处理程序(例如 POST 和 PATCH)上,我们能够使用服务器发送的请求 @Req()
装饰师。然而,还有更多的事情要做。
我们可以直接访问我们需要的请求对象的特定部分,而不是检索整个请求对象。
因此,Nest 提供了各种装饰器,可以与 HTTP 路由处理程序一起使用来访问 Fastify 对象的 Express:
巢装饰者 | Fastify 或 Express 访问的对象 |
`@Request(), @Req()` | `请求` |
`@Response()、@Res()` | `是` |
`@Next()` | `下一个` |
`@Session()` | `请求会话` |
`@Param(参数?:字符串)` | `req.params` / `req.params[参数]` |
`@Body(参数?: 字符串)` | `req.body` / `req.body[param]` |
`@Query(参数?:字符串)` | `req.query` / `req.query[param]` |
`@Headers(参数?:字符串)` | `req.headers` / `req.headers[param]` |
`@Ip()` | `请求.ip` |
`@HostParam()` | `req.hosts` |
一个典型的例子是替换 @Req()
我们之前使用的装饰器来访问结果的主体,其中 @Body()
这已经可以让我们直接访问请求的正文而无需钻取:
@Post()
store(@Body() body: any) {
return this.userService.create(body);
}
@Patch('/:userId')
update(@Body() body: any, @Param() param: { userId: number }) {
return this.userService.update(body, param);
}
create(body: any) {
return body;
}
update(body: any, param: { userId: number }) {
return { body: body, param };
}
在某些情况下,您可能只想检索请求负载的特定属性。在这种情况下,您必须定义数据传输对象 (DTO) 架构。数据传输模式是一个对象,它定义了正在检索的对象的副本,但主要用于在应该保存或检索的对象与持久层之间传输数据。通常,由于此过程更容易受到攻击 - DTO 不包含那么多敏感数据点。此特性还允许您仅检索对象的某些字段。
在 Nest 中,建议使用类来定义数据传输对象,因为类的值在编译期间会被保留。
假设请求正文有一个令牌,并且您不想检索或更新此类数据,则可以定义 DTO,如下所示:
@Patch('/:userId')
update(
@Body() updateUserDto: { name: string; email: string },
@Param() param: { userId: number },
) {
return this.userService.update(updateUserDto, param);
}
update(
updateUserDto: { name: string; email: string },
param: { userId: number },
) {
return { body: updateUserDto, param };
}
但是,您会注意到我们已经定义了类型 updateUserDto
两次;在 user.service.ts
和 user.controller.ts
,但我们需要保持代码干燥(不要重复自己),这样我们就不会在代码库中重复自己。
为此,在一个新文件夹中 /user/dto
,在 /user
目录下,我们需要创建一个文件 /update-user.dto.ts
与 .dto.ts
我们定义并导出的扩展名 UpdateUserDto
类用于在 user.service.ts
和 user.controller.ts
文件:
export class UpdateUserDto {
name: string;
email: string;
}
...
import { UpdateUserDto } from './dto/update-user.dto';
@Patch('/:userId')
update(
@Body() updateUserDto: UpdateUserDto,
@Param() param: { userId: number },
) {
return this.userService.update(updateUserDto, param);
}
...
import { UpdateUserDto } from './dto/update-user.dto';
update(updateUserDto: UpdateUserDto, param: { userId: number }) {
return { body: updateUserDto, param };
}
管道和验证
假设需要验证通过服务器发出请求时获取的数据。
在 Nest 中,我们可以通过使用安装两个依赖项的管道来测试进出应用程序的任何数据的正确性 - class-validator
和 class-transformer
.
管道是一个类,它定义为
@Injectable()
装饰器(因此,管道是提供者),它实现了PipeTransform
界面。它们将数据转换为所需的格式并评估数据,如果发现数据有效,则数据保持不变,否则将引发异常。为了使用管道,您需要将特定管道类的实例绑定到适当的上下文。
class-validator
包使得可以验证装饰器和非装饰器,使用 验证器 内部。虽然 class-transformer
包使得将对象转换为类的实例、将类转换为对象以及基于特定条件序列化或反序列化对象成为可能。
Nest提供的八个管道是:
ValidationPipe
ParseArrayPipe
ParseIntPipe
ParseUUIDPipe
ParseBoolPipe
DefaultValuePipe
ParseEnumPipe
ParseFloatPipe
为了在本指南中演示 Nest 中的验证,我们将使用内置 ValidationPipe
这使得可以对请求有效负载进行强制验证,并与 class-validator
包裹;具体规则在每个模块的数据传输对象/本地类声明中使用简单的注释进行声明。
开始使用内置 ValidationPipe
这是从出口 @nestjs/common
,让我们安装 class-validator
和 class-transformer
包:
$ npm i --save class-validator class-transformer
# Or
$ yarn add class-validator class-transformer
# Or
$ pnpm install class-validator class-transformer
接下来,导航到 main.ts
我们将在哪里绑定 ValidationPipe
在应用程序的根级别,以确保应用程序中的所有端点都受到保护,不会检索无效数据:
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
接下来,在每个模块的数据传输对象声明中,我们通过为每个单独的数据声明适当的数据检查来添加一些验证规则。在我们的例子中,我们将声明适当的验证规则 name
和 email
in UpdateUserDto
:
import { IsEmail, IsString } from 'class-validator';
export class UpdateUserDto {
@IsString()
name: string;
@IsEmail()
email: string;
}
@IsString()
装饰器检查给定的数据是否是真正的字符串,并且 @IsEmail()
验证器检查给定数据是否是电子邮件,否则返回 false 并引发异常。
现在,如果我们尝试制作一个 PATCH
请求用户个人资料,并输入数字而不是有效的电子邮件,例如,将引发异常:
有了这些,我们的 Nest 应用程序就得到了非常好的验证。
验证时使用 ValidationPipe
,也可以过滤我们不希望方法处理程序接收的属性。例如,如果我们的处理程序只期望 name
和 email
属性,但请求还包括 country
属性,我们可以删除 country
通过设置得到的对象的属性 whitelist
至 true
当我们实例化时 ValidationPipe
:
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);
await app.listen(3000);
}
bootstrap();
在方法参数级别绑定管道
管道也可以定义在 params
,还有。为此,我们将在方法的参数级别绑定管道。
在此之前,即使我们定义了 userId
是一个数字,您会注意到,如果我们向 userId
作为字符串,无论如何它都会成功:
为确保值 userId
必须始终是一个数字,我们将声明它绑定 getUser()
具有验证检查的方法处理程序可确保相同:
...
import { ParseIntPipe } from '@nestjs/common';
@Get('/:userId')
getUser(@Param('userId', ParseIntPipe) userId: number) {
return this.userService.getUser(userId);
}
getUser(userId: number) {
return { userId };
}
ParseIntPipe
定义内置 ParseInt Pipe 并确保其运行的数据必须是整数。
现在,当我们制作一个 GET
请求无效 userId
字符串“ab”,验证失败并抛出异常 400
状态码:
但对于数值,验证成功通过:
我们还可以相应地更新其他方法处理程序以确保正确的验证:
import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Patch,
Post,
Req,
} from '@nestjs/common';
import { Request } from 'express';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Get()
getUsers() {
return this.userService.get();
}
@Get('/:userId')
getUser(@Param('userId', ParseIntPipe) userId: number) {
return this.userService.getUser(userId);
}
@Post()
store(@Req() req: Request) {
return this.userService.create(req);
}
@Patch('/:userId')
update(
@Body() updateUserDto: { name: string; email: string },
@Param('userId', ParseIntPipe) userId: number,
) {
return this.userService.update(updateUserDto, userId);
}
@Delete()
delete(@Param('userId', ParseIntPipe) userId: number) {
return this.userService.delete(userId);
}
}
import { Injectable } from '@nestjs/common';
import { Request } from 'express';
import { UpdateUserDto } from './dto/user-update.dto';
@Injectable()
export class UserService {
get() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
}
getUser(userId: number) {
return { userId };
}
create(req: Request) {
return req.body;
}
update(updateUserDto: UpdateUserDto, userId: number) {
return { body: updateUserDto, userId };
}
delete(userId: number) {
return { userId };
}
}
现在,我们已经确保采用最佳实践技术来验证随时进入我们的应用程序的数据(可能来自外部源)。
结论
在本指南中,您已经能够了解 Node.js 块上的最新内容; Nest.js,以及帮助您开始使用它构建应用程序所需的所有内容。您已经了解了 Nest 是什么、它的功能、如何创建 Nest 项目、如何处理 Nest 应用中的传入数据以及如何验证传入数据。总之,您已经了解了任何 Nest 应用程序的构建块,以及每个组件为 Nest.js 应用程序带来的价值。
从这一点来看,关于使用 Nest 构建企业级应用程序,还有很多东西需要学习,但您已经能够成功地涵盖基本概念,这些概念可以帮助您启动并运行未来的所有内容。
请留意未来的新指南,我们将在其中学习如何使用 Nest 和 MySQL 构建 Restful API。
感谢您的阅读!