Nestjs🐺⚡ | Nodejs 框架(上)| 控制器、异常过滤器、提供程序
Nestjs 是一个服务器端框架,经常与术语“服务器端 Angular”混淆
尽管 Nest 遵循了 Google Angular 的模式和设计原则,但它的设计与 Angular 有很大不同
Nestjs 是传统 Node.js 服务器端工具和包的抽象层
因此请不要将其与 http 服务器(例如:express、koa、fastify、hapi 等)进行比较。Nestjs 实际上使用 express 和 fastify 作为其 http 服务器平台
Nestjs 集中了所有必要的技术/工具,以便使用 Nodejs 构建完美、可靠且持久的企业服务器。它与 Django、Spring Boot、Ruby on Rails 等服务器端框架并驾齐驱。
它遵循微服务架构,但也可以用于单片服务器
Nestjs 的特点:(来源:https://docs.nestjs.com)
- 可扩展、可靠、多功能、先进的框架
- 提供干净、直接且易于理解的架构
- 开箱即用:
- 依赖注入,
- 使用控制器的装饰器进行路由
- 安全性
helmet
,,cors
node-csrf
- 异常过滤器(未处理的异常层)
- 卫兵
- 使用提供程序将逻辑与控制器分离
- 强大的模块系统
- 生命周期事件等等
- 单元测试和集成测试
jest
支持super-test
- 它提供/支持(通过包):
- http 服务器 (express/fastify)
- GraphQL 服务器
- websocket 服务器 (socket.io/ws)
- 数据库 orm(sequelize/mongoose/typeorm/knex/prism)
- 使用以下方式验证请求主体
class-validator
- 缓存使用
cache-manager
- 任务调度使用
cron
- 任务队列使用
bull
&更多其他工具
这不是一个完整的教程。如果你想完整学习 Nestjs,请访问https://docs.nestjs.com。它包含有关 Nestjs 的完整详细信息。
这些名称和技术术语可能让人眼花缭乱,但其实实现起来相当简单。有些只需要 5-10 行代码就能实现。但对于企业服务器或用户群较小的服务器来说,它们都同样重要。Nestjs 为我们涵盖了架构和依赖项。
事实上,Nestjs 实际上可以帮助并指导我们作为新的后端开发人员使用所有重要的工具,并作为工具使用
Nestjs 也有一个强大的命令行工具,名为@nestjs/cli
。它可以帮助操作文件/模块。它有点像 Angluar 的命令行工具,但只处理文件和模块。它可以帮助你更有效地组织项目。
我将完成 Nestjs 教程的三部分。在第一部分或本部分中,我将仅介绍
Controllers
、ExceptionFilters
&Providers
但首先让我们使用以下命令创建项目:
$ npm install -g @nestjs/cli
$ nest new hello-world && cd hello-world
$ npm run start:dev
这将创建以下目录结构:
hello-world/
├── src/
│ ├── app.controller.ts
│ ├── app.controller.spec.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── app.service.spec.ts
│ └── main.ts
├── test/
│ ├── app.e2e-spec.ts
│ └── jest.e2e.json
├── .gitignore
├── .eslintrc.js
├── .prettierrc
├── README.md
├── nest-cli.json
├── package.json
├── package-lock.json
├── tsconfig.build.json
└── tsconfig.json
hello
现在让我们在里面创建一个目录,src
并在里面hello
为本教程创建 4 个文件
hello.controller.ts
hello.module.ts
hello.service.ts
hello-body.dto.ts
1. 控制器
控制器是 Nest 的构建块。控制器用于处理传入的请求。您可以使用 http 方法修饰符(例如 Get、Post、Put、Delete 等)和装饰器来定义路由路径。
控制器示例:
// hello.controller.ts
import {Controller, Logger, Get, NotFoundException, Param} from "@nestjs/common"
@Controller()
export class HelloController{
/* a logger from nestjs for logging error/other info */
logger: Logger = new Logger(HelloController.name)
db: {id: string, message: string}[] = []; // temporary database
@Get("hello")
async replyHello() {
try {
return "Hello";
} catch (error) {
this.logger.error(error?.message ?? "");
throw error;
}
}
@Get("hello/:helloId") // dyanmic parameter just like express, koa-router etc...
async replyExactHello(
/*pass the same dynamic parameter from "hello/:helloId" in
@Param decorator's first to let nestjs find the parameter
correctly
*/
@Param("helloId") id: string
) {
try {
/*returning the correct temp hello message*/
const message = this.db.find(hello=>hello.id===id)?.message
if(!message) throw new NotFoundException("desired `hello` not found") //404 error
return message;
} catch (error) {
/* will log the error & autmatically send the error as response with all required data */
this.logger.error(error?.message ?? "");
throw error;
}
}
}
Nestjs 使用装饰器模式,主要用Typescript编写,但也支持 JavaScript。你还可以使用以下命令对请求体进行验证:class-validator
////// main.ts //////
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./hello.module";
import { ValidationPipe } from "@nestjs/common";
async function bootstrap() {
/* Creates a nest application for monolithic servers */
const app = await NestFactory.create(AppModule, { logger: false });
// validation is done through Nestjs pipes.
// Nestjs Pipes run before a specified request (kinda like middlewre)
/* Enabling Validation pipe globally.
Thoough it can be done in module level too
*/
app.useGlobalPipes(new ValidationPipe());
await app.listen(PORT);
}
bootstrap()
这里我只是创建了一个 Nestjs 服务器实例并ValidatonPipe
全局添加。管道只是在控制器之前运行的方法。管道也可以通过装饰器在方法/参数级别使用。你甚至可以创建自己的自定义管道。你可能已经注意到了。这是 Nestjs 使所有功能正常运行的关键。你可以在“异常过滤器”部分之后找到更多关于它的@UsePipes
信息。AppModule
AppModule
class-validator
现在让我们用& 装饰器创建一个主体验证模式
////// hello-body.dto.ts //////
import {IsDefined, IsNotEmpty} from "class-validator"
export class HelloBodyDTO{
@IsDefined()
@IsNotEmpty({message: "A custom error message if you want to"})
message!: string;
}
@IsDefined
&@IsNotEmpty
将验证定义为 & 的字符串至少长度为 1,换句话说,该字符串不应该只是""
。现在让我们在请求控制器中使用它@Post
:
////// hello.controller.ts //////
import {Controller, Logger, Get, NotFoundException, Post, Body} from "@nestjs/common"
import {HelloBodyDTO} from "./hello-body.dto"
import {v4 as uuid} from "uuid"
@Controller()
export class HelloController{
// ... previously written stuff from the `Controller` part
// decorator name is similar to http verbs e.g. POST -> @Post
@Post("hello")
saveHello(
/*Just pass the class as a type & the validation will be done automatically*/
@Body() body: HelloBodyDTO
){
try{
const id = uuid()
const hello = {id, message: body.message}
this.db.push(hello) // saving in the temp db
return hello;
}
catch (error){
this.logger.error(error?.message ?? "");
throw error;
}
}
}
2. 异常过滤器
异常过滤器是控制器抛出错误时运行的错误处理程序。它会自动处理该错误并发送适当的、用户友好的错误响应。
如果您仔细阅读了代码,您可能会发现 Nestjs 默认HttpExceptionFilter
(全局)使用了 。该包@nestjs/common
提供了许多HttpException
继承的异常,例如NotFoundException
、BadRequestException
、等等。您甚至可以创建自己的自定义异常。NotAcceptableException
UnauthorizedException
ExceptionFilter
如果要在路由处理程序中使用自定义异常过滤器,则必须使用@UseFilter
装饰器
// ... other stuff
import {ForbiddenException} from "@nestjs/common"
import {CustomHttpExceptionFilter} from "./custom-filter"
// a simple handler method inside a Controller
@Post("throw-error")
@UseFilters(new CustomHttpExceptionFilter())
errorThrowingHandler(){
throw new ForbiddenException("its forbidden");
}
// ... other stuff
在每个处理程序/控制器之前使用这段长代码@UseFilters(new HttpExceptionFilter())
并不难,但如果您的应用程序有一个全局使用它的用例,那么您只需使用useGlobalFilters
Nestjs 服务器实例并将所有全局过滤器作为参数传递
///// main.ts /////
// ...other stuff
import {CustomHttpExceptionFilter} from "./custom-filter"
app.useGlobalFilters(new CustomHttpExceptionFilter())
// ...other stuff
3. 提供商
提供程序是 Nestjs 的另一个重要组成部分。到目前为止,我一直使用临时变量来存储数据。这就是为什么可以通过控制器处理程序来完成这么简单的事情。但对于更大、更复杂的逻辑,代码分离和重用会很困难。这时,提供程序就派上用场了……
@Injectable
你可以在类顶部使用装饰器声明/创建一个提供程序。然后可以通过提供程序进行依赖注入/日志记录等。
以下是一个提供程序示例。为了便于理解,我使用自定义变量作为数据库。但大多数情况下create
,find
、findById
、delete
、deleteById
等方法都是由数据库 ORM 提供的。因此,在实际场景中,这些方法无需在提供程序中实现。提供程序应该用于处理更复杂的逻辑。但为了演示,我们先将这些方法视为复杂的逻辑。
////// hello.service.ts ///////
import { Injectable } from "@nestjs/common"
import {v4 as uuid} from "uuid"
@Injectable()
export class HelloService{
db: {id: string, message: string}[] = []
async findById(id: string){
return this.db.find(hello=>hello.id===id)
}
async create(message: string){
const id = uuid()
const hello = {id, message}
this.db.push(hello)
return hello;
}
async deleteById(id: string){
this.db = this.db.filter(hello=>hello.id!==id)
return `DELETED node ${id} from db`
}
}
现在,让我们通过依赖注入来转换HelloController
使用HelloService
。但在我们将HelloService
其放入之前HelloModule
模块是一个带有
@Module()
装饰器注解的类。@Module()
装饰器提供 Nest 用于组织应用程序结构的元数据。每个应用程序至少有一个模块,即根模块。根模块是 Nest 构建应用程序图的起点——Nest 使用根模块来解析模块和提供商之间的关系以及依赖关系的内部数据结构。
该模块是帮助 Nest 构建依赖注入依赖关系图的主要部分。示例如下app.module.ts
:
////// app.module.ts //////
import { Module } from '@nestjs/common';
/*This is the base '/' controller */
import { AppController } from './app.controller';
/* basic provider for AppController */
import { AppService } from './app.service';
@Module({
/*this where descendent modules get added
we've to do this if we were importing another inside
an other module to be able to use its providers
*/
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
我们必须将Injectable
我们在模块中的控制器/提供程序内部使用的每个提供程序()/控制器添加到模块中。让我们将HelloService
&HelloController
放入HelloModule
:
////// hello.module.ts //////
import {Module} from "@nestjs/common"
import {HelloService} from "./hello.service"
import {HelloController} from "./hello.controller"
@Module({
/* put all providers that is under this module graph to help Nest to
inject those in the controllers
*/
providers: [HelloService],
/* put controllers here for letting Nest recognize all the route/path &
their handlers
*/
controllers: [HelloController],
/*put those providers which you wanna use outside of this module
In an outside module when HelloModule gets imported
*/
exports: []
})
export class HelloModule{}
为了让 Nest 识别HelloModule
为模块,让我们在数组HelloModule
中添加:imports
AppModule
///// app.module.ts //////
// ... previously imported stuff
import {HelloModule} from "./hello/hello.module"
@Module({
imports: [HelloModule],
// ... other properties previously added
})
export class AppModule {}
这
AppModule
被用作NestFactory
实例的入口点main.ts
现在我们可以轻松地在HelloService
内部HelloController
或其他模块的控制器/提供程序中使用
////// hello.controller.ts //////
// ... previously imported stuff
import {HelloService} from "./hello.service"
@Controller()
export class HelloController{
logger: Logger = new Logger(HelloController.name)
/* just create a contructor arg and set the type as the provider
& that's gonna do the work
*/
constructor(private readonly helloService: HelloService){}
@Get("hello")
async replyHello() {
try {
return "Hello";
} catch (error) {
this.logger.error(error?.message ?? "");
throw error;
}
}
@Get("hello/:helloId")
async replyExactHello(@Param("helloId") id: string) {
try {
/*using provider's methods*/
const message = await this.helloService.find(id)?.message;
if(!message) throw new NotFoundException("desired `hello` not found") //404 error
return message;
} catch (error) {
this.logger.error(error?.message ?? "");
throw error;
}
}
@Post("hello")
saveHello(@Body() body: HelloBodyDTO){
try{
/* creating `hello` using the provider HelloService */
return await this.helloService.create(body.message)
}
catch (error){
this.logger.error(error?.message ?? "");
throw error;
}
}
}
不要害怕 Nestjs 的模块系统。一开始可能会有点难,但一旦你理解了,一切都会变得有意义。而且 Nestjs 的所有酷炫的依赖注入功能都需要这个模块系统。
顺便说一句,您无需在模块中手动添加提供程序/控制器。如果您使用nest-cli 创建模块/提供程序/控制器,它将自动完成。上述模块管理步骤只需使用以下三个命令即可自动完成
创建一个模块:
$ nest g module hello
创建控制器:
$ nest g controller hello
创建提供者:
$ nest g provider hello
别伤害我😶。我知道,我应该早点展示这个更简单的方法😁。但是 Nestjs 中模块工作原理的这种想法常常困扰着人们,让他们不敢使用 Nestjs。所以深入了解它很重要。你可以在这里深入了解 Nestjs 模块系统。
这是完整的应用程序
文章来源:https://dev.to/krtirtho/nestjs-the-framework-of-nodejs-part-1-gl7