Quiet
  • HOME
  • ARCHIVE
  • CATEGORIES
  • TAGS
  • LINKS
  • ABOUT

李致知

  • HOME
  • ARCHIVE
  • CATEGORIES
  • TAGS
  • LINKS
  • ABOUT
Quiet主题
  • Node.js框架
  • 后端框架

NestJS

李致知
技术点

2023-02-01 14:32:14

文章目录
  1. ##我的笔记——源自技术胖的教程
  2. 一,nestJS简介
  3. 二,基本路由的创建
    1. 创建一个模块
    2. 独立业务逻辑
    3. 基本的Get和Post请求
      1. 一个简单的post请求(不带参数):
      2. 一个带参数的Get请求:
    4. 动态路由和多参数传递
  4. 三,数据库操作
    1. 安装
    2. 引入TypeORM
    3. 编写Entities实体
    4. 增删改查
  5. 四,依赖注入
  6. 五,热重载功能
  7. 六,中间件Middleware
  8. 七,模块Module

##我的笔记——源自技术胖的教程

一,nestJS简介

优缺点:

  • 代码架构合理,概念较全
  • TS原生支持,体验好,项目代码质量高
  • 学习坡度较高,上手有难度

安装:

前置环境:nodeJS、npm

npm i -g @nestjs/cli
nest --version  #测试一下安装成功了没
nest new nestjs-demo #新建项目
npm run start:dev #启动项目

打开src/main.ts可以看到监听端口为3000,我们打开http://localhost:3000就会看到hello world!这个字符串的输出源代码在app.service.ts中可以看到。

文件目录(我们只看src目录):

app.controller.spec.ts #对于控制器的单元测试文件
app.controller.ts #控制器文件,可以简单理解为路由文件
app.module.ts #模块文件
app.service.ts #服务文件,业务逻辑写在这里面
app.main.ts #入口文件,项目主模块和监听端口号写在这里面

二,基本路由的创建

//app.controller.ts
@Get('name')
getName():string{
    return "mlhiter"
}

我们浏览器访问http://localhost:3000/name即可

如果我们想要更改顶层路径,比如将http://localhost:3000/name 变成http://localhost:300/api/name我们只需要在@Controller()的括号里添加'api'即可。

创建一个模块

首先删除原有的app.controller.spec.ts和app.controller.ts和app.service.ts,然后将app.modlue.ts文件中的报错项都删掉。

# 打开终端
nest g module network #新建模块命令,network是模块名,脚手架会帮我们自动引入和创建
nest g controller network --no-spec #创建相应的控制器文件,这里后面的--no-spec是不生成spec单元测试文件,现在我们没必要有单元测试文件

然后进入network.controller.ts文件

import { Controller, Get } from '@nestjs/common';

@Controller('network')
export class NetworkController {
  @Get()
  getNetwork():any {
    return {
      code: 400,
      data: ['百度', '谷歌', '搜狗'],
      msg: '请求网络成功!',
    };
  }
}

我们输入http://localhost:3000/network来查看信息。

这个时候如果想要在所有模块的请求加入前缀/api,可以在src/main.ts里添加。

app.setGlobalPrefix('api');

独立业务逻辑

我们前面将具体的返回值放在了controller文件里,其实这样是不符合规范的,这种业务逻辑我们应该写在service文件里。

# 打开终端
nest g service network --no-spec #新建service文件 

我们有了service文件,就可以把它引入我们的controller文件里,所以进入network.controller.ts文件里,更改为:

import { Controller, Get } from '@nestjs/common';
import { NetworkService } from './network.service';
@Controller('network')
export class NetworkController {
  constructor(private networkService: NetworkService) {}
  //这句话的意思是this.networkService = new NetworkService;
  //......
}

具体逻辑我们就可以更换位置了。

//network.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class NetworkService {
  //修改如下
  getNetwork(): any {
    return {
      code: 400,
      data: ['百度', '谷歌', '搜狗'],
      msg: '请求网络成功!',
    };
  }
}

//network.controller.ts
import { Controller, Get } from '@nestjs/common';
import { NetworkService } from './network.service';
@Controller('network')
export class NetworkController {
  constructor(private networkService: NetworkService) {}
  @Get()
  getNetwork() {
    //这里修改引入service的方法
    return this.networkService.getNetwork();
  }
}

基本的Get和Post请求

一个简单的post请求(不带参数):
//network.service.ts
addNetwork():any {
    return {
      code: 200,
      msg: 'add请求成功!',
  };
}
//network.controller.ts
@Post("/add") //记得先引入,但是一般你用的话vscode会自动引入
addNetwork() {
  return this.networkService.addNetwork();
}

因为Post请求我们就不能通过访问网址来看结果了,这里使用vscode里的REST Client来进行请求。

使用方法参考这个博客:REST Client指南

使用方法就是在项目里任何位置新建一个xxx.http文件。

POST http://localhost:3000/api/network/add

我们可以通过###来分割一个文件中的不同请求。

一个带参数的Get请求:
//根据不同id查询不同网站
//network.service.ts
  getNetworkById(id: number) {
    let data: any = {};
    switch (id) {
      case 1:
        data = {  name: '百度' };
        break;
      case 2:
        data = {  name: '搜狗' };
        break;
      case 3:
        data = { name: '谷歌' };
        break;
    }
    return data;
  }
//network.controller.ts
@Get('/getNetworkById')
  getNetworkById(@Request() req): any {
    //这里的参数是个字符串,所以要转换成数字
    const id: number = parseInt(req.query.id);
    return this.networkService.getNetworkById(id);
  }
//这里的query也可以换一个简写的方法
  @Get('/getNetworkById')
  getNetworkById(@Query() query): any {
    const id: number = parseInt(query.id);
    return this.networkService.getNetworkById(id);
  }

测试:

GET http://localhost:3000/api/network/getNetworkById
?id=2

动态路由和多参数传递

动态路由就是将Get请求中的参数放在路径中而不是?之后的传统方式。

@Get('/findNetworkById/:id')
  findNetworkById(@Request() req): any {
    const id: number = parseInt(req.params.id);
    return this.networkService.getNetworkById(id);
  }
GET http://localhost:3000/api/network/findNetworkById/2

多个参数的话也很简单,直接跟在路径后即可,比如/findNetworkById/:id/:name,类似query也有简写方式

//这里的params也可以换一个简写的方法
@Get('/findNetworkById/:id')
  findNetworkById(@Param() params): any {
    const id: number = parseInt(params.id);
    return this.networkService.getNetworkById(id);
  }

Tip:headers信息获取可以直接通过Headers装饰器获取

三,数据库操作

ORM(object relational Mapping)的意思是”对象关系映射”,它可以非常方便地进行对象式的数据库操作,而不是直接使用复杂的SQL语句。这样的操作一个对象就对应一个表,这个对象的一个实例就是表的一条记录,对象的属性则对应着表中的一个字段。

优点:方便维护、代码少好写、自动化。

缺点:效率和性能相对原生SQL低一些。

常见的工具:prisma(公司研发,稳定)和TypeOrm(社区维护,性能更好)。

安装

我们使用nestjs官方推荐的TypeOrm。

npm install --save @nestjs/typeorm typeorm mysql2

小皮面板开启mysql或者本地自行开启mysql。

引入TypeORM

imports: [
    NetworkModule,
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'mlhiter',
      password: 'root123',
      database: 'test',
      retryDelay: 500, //重新链接数据库间隔
      retryAttempts: 10, //允许重连次数
      synchronize:true, // 是否将实体同步到数据库
      autoLoadEntities:true,  // 自动加载实体配置,forFeature()注册的每个实体都能自己加载
    }),
  ],

如果npm run start:dev之后没有报错,那么应该是连接成功了。

编写Entities实体

import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  CreateDateColumn,
  Generated,
} from 'typeorm';

@Entity()
export class Network {
  @PrimaryGeneratedColumn() //自动递增的id
  id: number;

  @Generated('uuid') //生成不规则不重复的自动编号
  uuid: string;

  @Column({ type: 'varchar', length: 255 }) //括号里定义数据的一些配置
  name: string;

  @CreateDateColumn({ type: 'timestamp' }) //获取数据生成时间
  entryTime: Date;
}

增删改查

//network.controller.ts
@Get('/add')
  addNetwork(@Body() body): any {
    console.log(body);
    return this.networkService.addNetwork();
  }
  @Get('/delete/:id')
  deleteNetwork(@Param() params): any {
    const id: number = parseInt(params.id);
    return this.networkService.delNetwork(id);
  }
  @Get('/update/:id')
  updateNetwork(@Param() params): any {
    const id: number = parseInt(params.id);
    return this.networkService.updateNetwork(id);
  }
  @Get('/get')
  getNetwork(): any {
    return this.networkService.getNetwork();
  }
  @Get('/findNetwork/:name')
  findNetworkByName(@Param() params): any {
    console.log(params.name);
    const name: string = params.name;
    return this.networkService.getNetworkByName(name);
  }

//network.service.ts
import { Injectable } from '@nestjs/common';
import { Repository, Like } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Network } from './entities/network.entity';
@Injectable()
export class NetworkService {
  constructor(
    @InjectRepository(Network) private readonly network: Repository<Network>,
  ) {}
  //增加数据
  addNetwork() {
    const data = new Network();
    data.name = 'mlhiter';
    return this.network.save(data);
  }
  //删除数据
  delNetwork(id: number) {
    return this.network.delete(id);
  }
  //修改数据
  updateNetwork(id: number) {
    const data = new Network();
    data.name = 'google';
    return this.network.update(id, data);
  }
  //查询全部数据
  getNetwork() {
    return this.network.find();
  }
  //模糊查询名字
  getNetworkByName(name: string) {
    return this.network.find({
      where: {
        name: Like(`%${name}%`),
      },
    });
  }
}

四,依赖注入

IOC(Inversion of Control,又称控制反转)是前端后端目前都在使用的技术,Vue3中也有依赖注入,使用非常普及。

在nestjs中最常见的情况就是service服务类具体逻辑注入到controller文件里使用。我们在controller文件里只需要调用this.networkService.xxx方法即可,具体方法是啥在service里写。

这里说了三种注入情况,一种是注入服务模块,一种是注入数据、一种是注入服务之外的逻辑方法。

//network.module.ts
  // providers: [NetworkService],
  providers: [
    {//注入模块
      provide: 'network',
      useClass: NetworkService,
    },//上面注释掉的是简写方式,那种方式不用写注入语句,而这种方式是完整的写法,需要写注入语句
    {//注入数据
      provide: 'networkArray',
      useValue: ['小红', '小翠', '大鹅'],
    },
    {//注入方法
      provide: 'myFactory',
      useFactory() {
        console.log('myFactory------');
        return '工厂函数已执行';
      },
    },
  ],
      
//network.controller.ts
//写到原来的构造函数里就行,注意Inject装饰器需要先引入
constructor(
    @Inject('network') private networkService: NetworkService,
    @Inject('networkArray') private networks: string[],
    @Inject('myFactory') private myFactory: string,
  ) {}  
//使用的话使用private声明的即可,注入名是module里的provide名称
@Get('/test')
  test(): string[] {
    console.log(this.myFactory);
    return this.networks;
  }

五,热重载功能

热重载:对比代码变化只重新编译变化的文件,节省开发时间。

NestJS对于热重载功能支持的不好,因为有些实体类文件和静态文件的支持性不好。

但是开发初期还是可以用的,大不了重新运行一下就好了。

1,依赖包安装:

npm i --save-dev webpack-node-externals run-script-webpack-plugin webpack

2,增加配置文件:

在根目录下新建一个webpack-hmr.config.js文件

/* eslint-disable @typescript-eslint/no-var-requires */
const nodeExternals = require('webpack-node-externals');
const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin');

module.exports = function (options, webpack) {
  return {
    ...options,
    entry: ['webpack/hot/poll?100', options.entry],
    externals: [
      nodeExternals({
        allowlist: ['webpack/hot/poll?100'],
      }),
    ],
    plugins: [
      ...options.plugins,
      new webpack.HotModuleReplacementPlugin(),
      new webpack.WatchIgnorePlugin({
        paths: [/\.js$/, /\.d\.ts$/],
      }),
      new RunScriptWebpackPlugin({
        name: options.output.filename,
        autoRestart: false,
      }),
    ],
  };
};

3,修改main.ts文件:

在监听端口下面增加代码

if (module.hot) {
    module.hot.accept();
    module.hot.dispose(() => app.close());
  }

报错则安装依赖包:

npm i -D @types/webpack-env

4,替换启动脚本:

将package.json里的启动脚本start:dev替换。

"start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch",

六,中间件Middleware

中间件通俗地说就是一个函数或者方法,你可以配置它给你任何想用在的地方。

局部中间件:

生成命令:

nest g mi counter

counter.middleware.ts文件:具体执行的业务逻辑

import { Injectable, NestMiddleware } from '@nestjs/common';

@Injectable()
export class CounterMiddleware implements NestMiddleware {
  use(req: any, res: any, next: () => void) {
    //在这里输出一个字符串,检测一下什么使用
    console.log('进入中间件');
    //next()方法是执行到这里继续下去,如果没有的话就会停在这里
    next();
  }
}

在network.module.ts中使用中间件。

import {CounterMiddleware} from '../counter/counter.middleware'
......
export class GirlModule  implements NestModule{
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(CounterMiddleware).forRoutes('network')
  }
}

全局中间件:

在main.ts中定义和使用全局中间件,他们可以在每个接口使用的时候都调用。

一般就是定义一个方法,然后使用app.use挂载到全局。

//main.ts
......
function MiddlewareAll(req:any,res:any,next:any){
    console.log('我是全局中间件......')
}
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  //在这里挂载 
  app.use(MiddleWareAll)
  await app.listen(3000);

  if (module.hot) {
    module.hot.accept();
    module.hot.dispose(() => app.close());
  }
}

第三方中间件:

使用cors中间件解决跨域请求。(其实NestJs有配置项,这里为了使用第三方中间件)

#安装依赖包
npm install cors
npm install @types/cors -D
//main.ts
import * as cors from "cors"
//跨域处理
async function boostrap(){
  app.use(cors());//注意这个放在中间件的上面,appd定义的下面
}

跨域其他知识:

  • 中间件里除了next()还有res.send(),是用来拦截非法访问的,我们可以在括号里填入字符串,用来进行对客户的提示。

  • 我们还可以指定什么请求类型通过中间件

    export class GirlModule  implements NestModule{
      configure(consumer: MiddlewareConsumer) {
     //只允许GET请求通过
          consumer.apply(CounterMiddleware).forRoutes({path:'network',method:RequestMethod.GET})
      }
    }
    

七,模块Module

模块就是module.ts文件。一般我们会把功能或者逻辑相关的方法放在一个模块里。

#生成样板模块
nest g res test

会自动在app.module.ts文件引入该模块

如果我们想在另一个模块内调用test模块的功能的话怎么办?

//test.module.ts
@Module({
    ...
    exports:[TestService],//导出业务逻辑功能
    ...
})
//network.module.ts
//导入test的服务模块
import {TestService} from "xxx"
@Module(({
    xxx
    providers:[TestService,xxx],
    xxx
}))
//network.controller.ts
import {TestService} from "xxxx"

constructor(
    private testService:Testservice
){}

全局模块:让其他所有的模块使用

新建一个config文件夹,然后在里面新建一个config.module.ts文件

import {Module,Global} from '@nestjs/common'

@Global()
@Module({
  providers:[{
    provide:"Config",
    useValue:{name:"mlhiter"}
  }],
  exports:[{
    provide:"Config",
    useValue:{name:"mlhiter"}
  }]
})
export class ConfigModule{}
//app.module.ts
import {ConfigModule} from "xxx"
@Module({
  imports: [ConfigModule,]
})
//network.controller.ts
 constructor(
    @Inject('Config') private  name:string,
  ){}

动态模块

//config.module.ts
export class ConfigModule{
 static forRoot (option:string):DynamicModule{
    return {//传入option使用
      module:ConfigModule,
      providers:[{
         provide:"Config",
    useValue:{name:"mlhiter"+option}
      }],
      exports:[{
         provide:"Config",
    useValue:{name:"mlhiter"+option}
      }]
    }
  }
}
//app.module.ts
@Module({
    imports:[ConfigModule.forRoot('xxx')]
})
上一篇

凡事做到底

下一篇

VScode相关

©2023 By 李致知. 主题: Quiet 鲁ICP备2022039519号-1
Quiet主题