##我的笔记——源自技术胖的教程
一,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')]
})