Nest-node版的Spring——(第一部分官方精简)
# 官方说明,学习
一点一点的学习,我们这里有三套的教程 ,建议顺序
框架的哲学
近几年由于 Node.js 的出现,JavaScript 成为了前端和后端的「lingua franca5」,前端方面出现了 Angular, React, Vue 等众多的 UI 框架,后端方面也有像 Express, Koa 这样优秀的框架出现,但这些框架都没有高效地解决一个核心问题 — 架构,而我nest就是这样的一个框架
开始
新建一个项目
请确定,你在本地安装了 nest的cli npm i -g @nestjs/cli就好了
npm i -g @nestjs/cli
nest new project-name // 创建一个新的项目
这个时候我们进入,3000端口就能看到东西了
Hello word
重点说明 main.js入口
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 2.这样就指定了编译平台
import { NestExpressApplication } from '@nestjs/platform-express'
// import { NestFastifyApplication } from '@nestjs/platform-fastify'
async function bootstrap() {
// 1.指定设使用说明品台,二选一,Express+Fastify
// 3.总结:一般情况下,我们是不写的
const app = await NestFactory.create<NestExpressApplication>(AppModule);
await app.listen(3000);
}
bootstrap();
MVC中的C,控制器
初始化
我们来看看mvc中的控制器,由控制器来负责,处理客户端请求并发送响应内容,就是负责处理指定请求与应用程序的对应关系,路由则决定具体处理哪个请求。
使用@装饰器,来进行限定路由的功能
装饰器,有给类指定的,也有给方法指定的
我们需要使用ng cli的指令来生成一个控制器,它配套会生成一些我们需要的东西,
# 假设我们生成一个ctas 猫咪控制器
nest g controller cats
# 产生的代码
CREATE src/cats/cats.controller.spec.ts (479 bytes)
# 文件是用于写测试用例
CREATE src/cats/cats.controller.ts (97 bytes)
# 生成了一个控制器
UPDATE src/app.module.ts (322 bytes)
# 更新了app.module.ts 模块
- 看一下我们生成的项目解构
-|src
-|------| cats
-|------------|cats.controller.spec.ts
-|------------|cats.controller.ts // 主要的业务逻辑
-我们往里面丢一些代码逻辑
/cats.controller.ts
import { Controller, Get } from '@nestjs/common';
@Controller('cats') //类装饰器
export class CatsController {
@Get() //方法装饰器 表示 HTTP 请求装饰器
// 当你需要自定义状态码的时候,默认是200
@HttpCode(201)
findAll(): string {
// 如果返回一个 JavaScript 对象或者数据,将自动序列化成 JSON。
return 'This action returns all cats';
// 当我们访问3000/cats的时候就会走到这里来,这里就是一个路由
}
}
处理请求参数
处理请求参数是非常正常的操作,有是在Node中至关重要的一件事情
-
我们使用装饰器来处理这些不同的东西
@Request() req
@Response() res
@Next() next
@Session() req.session
@Param(key?: string) req.params / req.params[key]
@Body(key?: string) req.body / req.body[key]
@Query(key?: string) req.query / req.query[key]
@Headers(name?: string) req.headers / req.headers[name] -
如何获取到其它的方法呢?很简单,他们都有自己各自的装饰器
@Put(), @Delete(), @Patch(), @Options(), @Head(), and @All(),
接下俩我们定义一个Delete还有另一个带地址的@get 还有@Post()
/cats.contarll.ts
// 测试一下如何搞匹配地址
@Get('hello')
Hellow(){
return '哈哈哈哈'
}
// 测试获取post参数
@Post()
getPost( @Body() s:Object ){
return s
}
与之相互对应的http测试
### 测试带路劲的
GET http://127.0.0.1:3000/cats/hello
### 测试post
POST http://127.0.0.1:3000/cats
Content-Type: application/json
{
"name":"zhanhsang",
"age":"18"
}
- 我们来看一到底如何获取到这些参数,特别是post 还有get 还req.params req.query req.params
首先是@Query(key?: string)
/cats.contarll.ts
@Get()
@HttpCode(201)
// 注意这个findAll不是http的方法,是Nest中获取所有请求的方法
findAll( @Query() q:string ): string {
console.log(q);
// 1. 获取req.qury中的参数,类似于req.query,
// ====> http://127.0.0.1:3000/cats?test=hahahah,这个后面可以跟很多的参数
// { teste: 'hahah', hahah: '99999' }
return 'This action returns all cats';
}
补充说明params和query
req.params
http://localhost:3000/10
app.get("/:id",function (req,res) {
res.send(req.params["id"]);
// res.send(req.params.id)
});
req.query
http://localhost:3000/?id=10
app.get("/",function (req,res) {
res.send(req.query["id"]);
});
建议安装@types/express 包来获取 Request 的相关类型提示
- 如何处理动态的路由呢?我是说get/1 和 get/3 get/454656都是一个控制器
好办:id就好了,这个时候就需要@Param了
/cats.contarll.ts
@Get(':id')
testDynamic( @Param() params:Object ):Object {
return params
}
// 注意啊,由于你与爱心你定义了一个:id 那么后续的:hhaha将不会呗接受
@Get(':id')
testDynamic( @Param() params ):Object {
console.log(params.id);
return params
}
// 错误的写法
@Get(':hahaha')
testDynamic1( @Param('hahaha') id ):Object {
console.log(id);
return id
}
#### 第二轮测试
GET http://127.0.0.1:3000/cats/testDynamic
#### 测试
GET http://127.0.0.1:3000/cats/hahaha
结果
{
"id": "testDynamic"
}
{
"id": "hahah"
}
Provideru依赖注入的设计理念 Providers = 提供者
Providers 主要的设计理念来自于控制反转(Inversion of Control,简称 IOC )模式中的依赖注入(Dependency Injection)特性。
装饰器方法会优先于类被解析执行
Nest 实现注入是基于类的构造函数的
在我们这一章中,我们将会学习如下的知识点
- Services
- 依赖注入
- 注入作用域
- 自定义的 Providers
- 可选的 Providers
- 基于属性的注入
- 注册 Provider
我们在学习的之前需要了解nest的一个架构思维,spring
Services
实际上,这一章节讲得最多得还是这个内容就是把,服务Services写出来,而且
Services中的知识点都是围绕着,Provider 走的
- 先来看看我们的几个核心为Serive服务的文件
- 类型文件,这里全是与cast模块相关的接口类型
/cat.inteface.ts
// 在这个文件夹下,我们来实现与cats模块相关的各个接口
export interface Cat {
name: string;
age: number;
breed: string;
}
- 服务本体
/cats.serveoc.ts
import { Injectable } from '@nestjs/common';
import { Cat } from './interface/cat.interface'
@Injectable() // Injectable,可以注射的。一开始就声明了 你是可到处注射的,服务Service
export class CatsService {
private readonly cats: Cat[] = []; //定义一个只读属性,它实现了Cat接口
// 定义两个方法,创建还有获取
create(cat: Cat) {
this.cats.push(cat);
console.log( this.cats,'添加成功' );
}
findAll(): Cat[] {
return this.cats;
}
}
建议使用cil生成,serveice.ts
nest g service cats
- 去控制器中注入就好了
import { Controller, Get, HttpCode, Query, Post, Body, Param } from '@nestjs/common';
// 依赖注入我的 业务逻辑处理Service
import { CatsService } from './cats.service';
import { Cat } from './interface/cat.interface';
@Controller('cats') //类实例化之前就告诉了这个类,你是一个控制器
export class CatsController {
// 控制器汇总引用我们的seveice
private readonly catsService: CatsService
constructor(catsService: CatsService) {
this.catsService = catsService
}
@Post('create')
async create(@Body() createCatDto:any) {
this.catsService.create(createCatDto);
}
@Get('getAll')
async All(): Promise<Cat[]> {
return this.catsService.findAll();
}
+++++++
- 测试效果
POST http://127.0.0.1:3000/cats/create
Content-Type: application/json
{
"name":"loawng",
"age":18,
"breed":"2020-1-1"
}
### 测试
GET http://127.0.0.1:3000/cats/getAll
[ { name: 'laoli', age: 18, breed: '2020-1-1' } ] 添加成功
[
{"name":"laoli","age":18,"breed":"2020-1-1"},{"name":"loawng","age":18,"breed":"2020-1-1"}
]
依赖注入
所谓的依赖注入,就是找了一个IOC Container中间商
注入作用域
Providers是有生命周期的,当app启动的时候,就会不初始化,我们这里先不详细的介绍
自定义的 Providers
这里的表达的是你可以自定义的整一个IOC Container中间商,用来管理各个业务模块之间的依赖,这里先了解
可选的 Providers
当你需要一个依赖的时候,这个依赖并不一定呗nest内置的ioc解析,我们可以用@Optional()来装束一个非必须的参数
@Injectable()
export class HttpService<T> {
constructor(
@Optional()
@Inject('HTTP_OPTIONS')
private readonly httpClient: T
) {}
}
基于属性的注入
这里想表述的是这个意思,当多个Providers 相互依赖的时候,
通过在子类的构造函数中调用 super() 方法并不是很优雅,为了避免这种情况我们可以在属性上使用 @Inject() 装饰器。
@Injectable()
export class HttpService<T> {
@Inject('HTTP_OPTIONS') // 注入你父类上的某个方法
private readonly httpClient: T;
}
注册 Provider
我们的控制器是Serivce的消费者,
我们需要将这些 Service 注册到 Nest 上,注意啊这里指的是注册到nest上,让它全局可用
/app/modelus.ts
// 引入装饰器
import { Module } from '@nestjs/common';
// 基础的一个控制器还有与之相关的注入的依赖
import { AppController } from './app.controller';
import { AppService } from './app.service';
// 第二个 控制器还有与之相关的注入的依赖
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
@Module({
imports: [],
// 通过装饰器,全部完成注册
controllers: [AppController, CatsController],
providers: [AppService, CatsService],
})
export class AppModule {}
M模块
模块用来统筹所有的contarll还有serveice,有单独的服务于一个一个cotrlarr的模块,还有一个根应用的模块,这些模块是一个一个具体的业务功能所在
controller负责把模块一个一个组装起来
一个使用了 @Module() 装饰的类。@Module()
初始化中的app.modulde模块
基础的构建模块
我们先来初始化中的模块app.moudel长啥样,然后我们再来看如何把cats模块给抽离出来
/app.modelu.ts
// 引入装饰器
import { Module } from '@nestjs/common';
// 基础的一个控制器还有与之相关的注入的依赖
import { AppController } from './app.controller';
import { AppService } from './app.service';
// 第二个 控制器还有与之相关的注入的依赖
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
@Module({
imports: []
controllers: [AppController, CatsController], // 控制器集合
providers: [AppService, CatsService], // 可以被 Nest 的注入器初始化的 providers,至少会在此模块中共享,说白了就是被注入的要在模块中共享的
})
export class AppModule {}
如何封装自己的cats模块呢?实际上也非常的简单
使用cli快速构建,当然你也可以自己一个一个的敲,但是不建议
nest g module cats
# 这样就生成了一个初始状态的cats.modelu.ts 它和我们的cats是配套的
刚开始的时候,啥都没有
import { Module } from '@nestjs/common';
@Module()
export class CatsModule {}
我们把相关依赖的的丢进去
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers:[CatsController],
providers:[CatsService]
})
export class CatsModule {}
在我们执行cli的时候有一个细节,就是我们的app.moludel被更新了
更新了吧模块注入的代码
// 引入装饰器
import { Module } from '@nestjs/common';
// 基础的一个控制器还有与之相关的注入的依赖
import { AppController } from './app.controller';
import { AppService } from './app.service';
// 第二个 控制器还有与之相关的注入的依赖
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
// 被注入了进来,cats相关的模块被注入了进来 <====================
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule], // 引用的其它模块的集合, <====================
// 通过装饰器,全部完成注册
controllers: [AppController, CatsController], // 控制器集合
providers: [AppService, CatsService], // 可以被 Nest 的注入器初始化的 providers,至少会在此模块中共享,说白了就是被注入的要在模块中共享的
})
export class AppModule {}
如果我有个模块需要共享给其它模块使用呢?
实际上也非常的简单,我们只需要在需要共享的模块中加一个属性,如何在接受的模块也导入一下就好了
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
imports:[], // 我没有什么是需要依赖其它的模块的,所以这里为空
controllers:[CatsController],
providers:[CatsService],
exports:[CatsService]
})
export class CatsModule {}
问题来了哈,假设我这儿有这样的事情哈我希望 a --> b ---> c 我的意思是 a被我使用了,如何我使用的时候对a做了一定的数据附加,然后我又想丢给c用这个已经改造过的a 那么怎么操作呢?
重复倒出就好了
imports: [CommonModule],
exports: [CommonModule],
模块的依赖注入
假设 ,现在啊,有这样的事情,需要你在a模块中注入一些属性,配置一些参数,以便于a模块对不同的场景做出不同的反应
// 非常的简单,我们说过了,万物皆对象,在ts中万物皆类,计入是@Moulde修饰的,那么它本身也是一个类,是类就有对应的构造器,于是乎
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {
constructor(private readonly catsService: CatsService) {} // 这样就好了
}
全局的模块,还有动态的模块
- 全局的模块
全局的模块,我们使用@Global装饰一下就好了。这样全局就都可以用了,非常的简单
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
- 动态的模块
如何让模块适应不同的业务场景呢?依赖注入是一种方式,这个也是一种更加优雅的方式
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers'; // 自定义的注射器💉
import { Connection } from './connection.provider'; // 自定义的注射器💉
@Module({
providers: [Connection], //模块被注射了
})
export class DatabaseModule {
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}
理解就好了,我们后续来详细的说明如何使用
说明 模块的静态方法 forRoot 返回一个动态模块,可以是同步或者异步模块。
中间件
和express中的中间件是一毛一样的
这里我们就来造一个logger日志记录中间件
首先我们用类来实现一个中间件
/ logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
console.log('Request...');
// 我们给 请求的数据带上一点点标记 注意不能 req.XX = XXX 去加,是加不上去的
let Name_Scope = { bmlaoli:{age:18} }
Object.assign(req,Name_Scope )
next();
}
}
同样的道理,这里也是支持依赖注入了,这里不详细的说明了
如何使用中间件?
我们可以在具体的路由中,中使用,也可以使用通配符统配调一些路由,也可以全局的使用
- 中间件有带你特殊,我们不能直接在@model中列出来,我们只能 用模块类的 configure() 方法来设置它们
/app.module.ts
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('cats');
// .forRoutes({ path: 'cats', method: RequestMethod.GET }) 再具体匹配一些路由和方法,我们还可以使用通配符,这不详细的说明
}
}
中间件消费者
MiddlewareConsumer 是一个帮助类。它提供了几种内置方法来管理中间件。他们都可以被简单地链接起来。forRoutes() 可接受一个字符串、多个字符串、对象、一个控制器类甚至多个控制器类。
/app.module.ts
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware) // 用来搬定多个中间件
.forRoutes('cats');
// .forRoutes({ path: 'cats', method: RequestMethod.GET }) 再具体匹配一些路由和方法,我们还可以使用通配符,这不详细的说明
}
}
函数式中间件,这里不讲了
全局挂载中间件,全局挂载中间件还是比较有意思的
直接往app上丢就好了
maint.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 2.这样就指定了编译平台
import { NestExpressApplication } from '@nestjs/platform-express'
// import { NestFastifyApplication } from '@nestjs/platform-fastify'
import { LoggerMiddleware } from './cats/middleware/logger.middleware'
async function bootstrap() {
// 1.指定设使用说明品台,二选一,Express+Fastify
// 3.总结:一般情况下,我们是不写的
const app = await NestFactory.create<NestExpressApplication>(AppModule);
const logger = new LoggerMiddleware() // 类 实例化 ===> 对象 ===> 对象中有方法
app.use( logger.use )
await app.listen(3000);
}
bootstrap();