初始化项目
bash
npm i -g @nestjs/cli
nest new project-name
文件结构
- app.module.ts:应用程序的根模块
- app.controller:服务的消费者
- app.service.ts:服务的提供者
- app.controller.spec.ts:单元测试文件
运行程序
bash
npm run start:dev # 可以使用热更新
基本概念
Controller
控制器负责处理传入请求并将响应返回给客户端,可以简单理解为控制器就是API接口,控制器的用途是接收应用程序的特定请求。
路由
路由机制控制哪个控制器接收哪些请求。通常,每个控制器都有多个路由,不同的路由可以执行不同的操作。例如:在 User 控制器当中,我们可以做用户相关的许多操作,这些操作我们可以进一步区分成各种路由,例如查询所有的用户、根据用户的 id 查询用户信息等等
我们可以使用如下命令快速创建一个 controller,例如要创建一个叫 user 的 controller
bash
nest g controller [name]
nest g controller user # 创建叫 user的 controller
然后就会生成一个单元测试文件,和 controller
在 user.controller.ts中编写如下代码
ts
import { Controller, Get } from '@nestjs/common';
@Controller('user')
export class UserController {
@Get()
findAll(): Object {
return {
name: "Tim",
age: 18
}
}
}
不难看出,我们首先要自定义一个新的接口,在@Controller 装饰器中填写路径名称,在下方写上不同接口的请求方式和对应的方法
kotlin
@Controller(路径)
export class xxx
@请求方式
函数: 返回类型
路径参数
如果我们还需要增加一个根据 id 查询的接口怎么办?,例如:user/id/1
,user/id/2
ts
import { Controller, Get, Param } from '@nestjs/common';
@Controller('user')
export class UserController {
@Get()
findAll(): Object {
return {
name: 'Tim',
age: 18,
};
}
@Get('id/:id')
findUseById(@Param() params: any): Object {
return {
id: params.id,
};
}
}
也可以这样写
ts
@Get('id/:id')
findUseById(@Param('id') id: number): Object {
return {
id: id,
};
}
请求对象
我们如何实现获取请求体、查询参数等数据?
我们可以使用一个使用一个 Request 对象获取所有的请求的内容
ts
import { Request } from 'express';
import { Controller, Get, Param, Req } from '@nestjs/common';
...
@Get('request')
getData(@Req() request: Request): Object {
return {
'body': request.body,
'params': request.params,
'query': request.query,
'headers': request.headers,
'cookies': request.cookies,
'signedCookies': request.signedCookies,
'method': request.method,
'url': request.url,
'path': request.path,
'hostname':request.hostname,
}
}
当然,也可以使用特定的装饰器,获取对应的数据
这里举个其他的例子,例如,访问http://localhost:3000/user/query?name=jack&&age=18
,返回{"name": "jack","age": "18"}
ts
@Get('query')
query(@Query() query: any): Object{
return query;
}
所以你可以根据不同的装饰器去获得你想要的数据,当然 all in reqeust 也可以,只是可能有些不优雅
资源(不同的请求方式)
就是这么简单。Nest 为所有标准 HTTP 方法提供了修饰器: @Get()
、 @Post()
、 @Put()
、 @Delete()
、 @Patch()
@Options()
和 @Head()
。此外, @All()
还定义了一个处理所有这些问题的办法。
ts
@Post()
create(): string {
return 'This action adds a new person';
}
路由通配符
nestjs还支持基于模式的路由。例如,星号用作通配符,将匹配任何字符组合
访问http://localhost:3000/user/name123
,不管 name 之后都是什么都会进入这个路由
ts
@Get('name*')
everyOne(): string{
return 'everyone'
}
负载参数
定义一个 userDto,Dto 类似于 ts 中的 interface,但是为什么不使用 ts 呢,以下是官方的回答
DTO 是一个对象,用于定义如何通过网络发送数据。我们可以通过使用 TypeScript 接口或简单的类来确定 DTO 模式。有趣的是,我们建议在此处使用类。为什么?类是 JavaScript ES6 标准的一部分,因此它们在编译的 JavaScript 中保留为真实实体。另一方面,由于 TypeScript 接口在转译过程中被删除,因此 Nest 无法在运行时引用它们。这很重要,因为当 Pipes 等功能在运行时可以访问变量的元类型时,它们会实现其他可能性。
ts
export class userDto {
name: string
age: number
}
使用 Body 装饰器来接收这个请求参数
ts
@Post('new')
createUser(@Body() user: userDto): string {
return `created user ${user.name}`;
}
测试结果如下,成功响应了
启动和运行
控制器始终属于一个模块,这就是为什么我们在装饰器中 @Module()
包含 controllers
的原因
ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserController } from './user/user.controller';
@Module({
imports: [],
controllers: [AppController, UserController],
providers: [AppService],
})
export class AppModule {}
我们使用 @Module()
装饰器将元数据附加到模块类,Nest 现在可以轻松反映必须挂载哪些控制器
Providers
提供程序是 Nest 中的一个基本概念。许多基本的 Nest 类可以被视为提供者 - 服务、存储库、工厂、帮助程序等。提供程序的主要思想是它可以作为依赖项注入
使用命令创建一个 service
bash
nest g service [name]
nest g service user // 创建 user.service
接下来就在这个 service 中模拟一个数据的检索和存储
因为要模拟一个检索和存储,需要先定义一下这个模拟数据的结构
ts
export interface User {
name: string;
age: number;
}
然后在 service 中写好创建和查询的方法,你可以注意到有个@Injectable()
的装饰器,使用了这个装饰器,这个依赖就会被 nestjs 的 IOC 容器进行管理,如果你学过 springboot,那么 IOC 的概念是差不多的,就是把实例化的控制权交给了框架,通过IOC去管理实例的生命周期,包括实例化、创建、使用、依赖、销毁等
如果对 IOC 感兴趣可以看看文章: en.wikipedia.org/wiki/Invers...
修改 service 为如下的代码,模拟检索和存储
ts
import { Injectable } from '@nestjs/common';
import { User } from './interface/user.interface'
@Injectable()
export class UserService {
private readonly user: User[] = []
create(user: User){
this.user.push(user)
}
findAll(): User[]{
return this.user
}
}
如何注入依赖并使用?
我们说了 provider 是一个提供服务能力的东西,我们使用类去定义了他可以提供的功能,然后 controller 中就可以使用这些功能
在 controller 中创建一个构造器,该构造器接收一个我们刚刚定义的 service
ts
constructor (private userService: UserService) {}
service 是使用了 IOC 容器进行管理的,我们无需考虑去实例化他,可以直接使用
ts
@Get()
findAll(): Object {
return this.userService.findAll();
}
可选依赖
有时,可能存在不一定必须解析的依赖项。例如,某个类可能依赖于配置对象,但如果未传递任何配置对象,则应使用默认值。在这种情况下,依赖项变为可选,因为缺少配置提供程序不会导致错误。
ts
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}
它接受一个通过 Inject('HTTP_OPTIONS')
装饰器注入的参数,这个参数通常是一个配置对象,它包含了创建 HTTP 客户端所需的所有选项。@Optional()
装饰器表示这个参数是可选的,如果在依赖注入过程中没有找到对应的提供者,httpClient
将被设置为 undefined
。
@Inject('HTTP_OPTIONS')
表示 NestJS 将尝试解析名为HTTP_OPTIONS
的提供者,并将其注入到httpClient
属性中。private httpClient: T
定义了一个私有属性httpClient
,它的类型是泛型参数T
,这个属性将被用来存储注入的 HTTP 客户端实例。
基于属性的依赖注入
到目前为止,我们使用的技术称为基于构造函数的注入,因为提供程序是通过构造函数方法注入的。在一些非常特殊的情况下,基于属性的注入可能是有用的。例如,如果顶级类依赖于一个或多个提供程序,则通过从构造函数调用 super()
子类来一直传递它们可能会非常繁琐。为了避免这种情况,可以在属性级别使用 @Inject()
装饰器。
ts
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
@Inject('HTTP_OPTIONS')
private readonly httpClient: T;
}
Moudles
模块是用 @Module()
装饰器注释的类。 @Module()
装饰器提供 Nest 用来组织应用程序结构的元数据。
每个应用程序至少有一个模块,即根模块。根模块是 Nest 用来构建应用程序图的起点,是 Nest 用来解析模块和提供程序关系和依赖关系的内部数据结构。强烈建议将模块作为组织组件的有效方法。因此,对于大多数应用程序,生成的架构将采用多个模块,每个模块都封装了一组密切相关的功能。
@Module()
装饰器采用单个对象,其属性描述模块:
- providers: 将由 Nest 注入器实例化的提供程序,并且至少可以在此模块之间共享
- controllers: 此模块中定义的必须实例化的控制器集
- imports:导出此模块中所需的提供程序的导入模块列表
- exports:
providers
该模块的子集由此模块提供,并且应该在导入此模块的其他模块中可用
功能模块
我们之前使用命令创建模块的时候,实际上,它帮我们在 app.module.ts 配置好了,所以我们才可以进行访问到 user 里面的内容
ts
@Module({
imports: [],
controllers: [AppController, UserController],
providers: [AppService, UserService],
})
那么为了更好地去管理不同的模块,每个模块都应该有一个 module 所以我们进行抽离
ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
@Module({
imports: [],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
也可以使用命令
ts
nest g module [name]
nest g module user
我们需要做的最后一件事是将这个模块导入到根模块( app.module.ts
文件中定义的 AppModule
)
ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
@Module({
imports: [UserModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
共享模块
在 Nest 中,模块默认是单例,因此可以毫不费力地在多个模块之间共享任何提供程序的同一实例。
很简单,我们只需要在 module 中的 exports 中导出即可,任何导入的 UserModule
模块都可以访问 UserService
并且将与导入它的所有其他模块共享同一个实例。
ts
@Module({
imports: [],
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
模块再导出
可以将一个模块导入后,又进行一次导出
ts
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class CoreModule {}
全局模块
当您想要提供一组开箱即用的提供程序(例如,帮助程序、数据库连接等)时,请使用 @Global()
装饰器使模块全局化。
python
import { Module, Global } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
@Module({
imports: [],
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
@Global()
装饰器使模块具有全局范围。全局模块只能注册一次,通常由根模块或核心模块注册。UserModule
被标记为全局模块。这意味着一旦它在任何地方被导入一次,UserService
就可以在整个应用程序中被注入和使用,无需在每个模块中再次导入UserModule
。
动态模块
这些模块可以动态注册和配置提供程序
ts
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';
@Module({
providers: [Connection],
exports: [Connection],
})
export class DatabaseModule {
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}
该
forRoot()
方法可以同步或异步(即通过Promise
)返回动态模块。
代码解释:
-
导入必要的依赖:
Module
和DynamicModule
是从@nestjs/common
包中导入的装饰器,用于定义NestJS模块。createDatabaseProviders
是一个函数,用于创建数据库提供者。这个函数可能是自定义的,用于生成数据库连接所需的提供者。Connection
是从./connection.provider
文件中导入的类,可能是用于创建和管理数据库连接的提供者。
-
定义
DatabaseModule
类:@Module()
装饰器用于定义NestJS模块。这里,它接受一个对象,包含providers
和exports
属性。providers: [Connection]
表示这个模块提供Connection
服务。exports: [Connection]
表示这个模块导出Connection
服务,使其可以在其他模块中被注入和使用。
-
静态方法
forRoot
:forRoot
是一个静态方法,用于动态创建模块。它接受两个参数:entities
(实体数组)和options
(配置选项)。const providers = createDatabaseProviders(options, entities);
调用createDatabaseProviders
函数,根据提供的选项和实体创建数据库提供者。- 方法返回一个
DynamicModule
对象,其中包含模块的定义、提供者和导出的提供者。
如果要在全局作用域中注册动态模块,请将 global
该属性设置为 true
ts
{
global: true,
module: DatabaseModule,
providers: providers,
exports: providers,
}
DatabaseModule
可以按以下方式导入和配置:
ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
@Module({
imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}
如果要反过来重新导出动态模块,可以省略 exports 数组中 forRoot()
的方法调用:
ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
@Module({
imports: [DatabaseModule.forRoot([User])],
exports: [DatabaseModule],
})
export class AppModule {}