在开始学习之前,我们首先准备下开发环境:
- Node:
16.20.2
- 包管理器:
pnpm
- nestjs版本:
10.2.1
- 全局安装nestjs命令行:
pnpm add -g @nestjs/cli
一、初始化项目
执行nest new nestjs-blog
,系统会自动创建一个nestjs
项目,并安装好好所有依赖项目。
初始的项目目录结构如下:
然后我们执行pnpm start:dev
启动项目,访问localhost:3000
,就可以看到项目已经正常启动。
文件说明:
/src/main
:项目入口文件。用于启动整个项目,可以配置/src/app.module.ts
:模块。整个项目可以划分为多个独立的模块,作为每个模块的入口文件,负责引入其他模块、注入服务、导出服务、配置控制器。app.module.ts
作为整个项目的基石模块,还负责引入项目中所有的模块、服务、控制器。/src/app.service.ts
:服务。用于存放本模块中的所有业务逻辑/src/app.controller.ts
:控制器。用于配制路由、鉴权、入参、返回数据等
二、依赖注入
1、服务注入
最常见的就是本模块中的服务,在模块中注入,然后在控制器中声明并引用。这里就拿app.module.ts
文件举例:
//app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],//注入AppService服务
})
export class AppModule {}
//app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
//在构造函数中直接声明
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
//app.service.ts
import { Injectable } from '@nestjs/common';
//服务中必须要使用Injectable装饰器,声明此服务可以被注入
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
2、自定义服务名称
在app.module.ts
文件中,注入服务时,上面的方式是直接在providers
中配置服务类型就可以了,系统会自动到 服务注册容器 中去检索对应的服务。如下:
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],//直接写服务类名称
})
完整的写法是这样的:
@Module({
imports: [],
controllers: [AppController],
providers: [{
provide: AppService, //声明服务名称
useValue: new AppService(),//声明服务使用的服务类
}],
})
当然我们也可以自定义服务名,如下:
//app.module.ts
@Module({
imports: [],
controllers: [AppController],
providers: [{
provide: "app",//这里改成字符串名称
useValue: new AppService(),
}],
})
//app.controller.ts
export class AppController {
constructor(@Inject("app") private readonly appService: AppService) { }
...
}
可以看到在app.module.ts
中声明了服务字符串名称:provide: "app"
,然后在控制器中调用时,就需要在构造函数入参中添加@Inject("app")
。
3、注入自定义数据
useValue
中返回的数据可以是自定义的任何数据。
//app.module.ts
@Module({
imports: [],
controllers: [AppController],
providers: [{
provide: "app",
useValue: { name: "IT飞牛" },//自定义数据
}],
})
//app.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
@Controller()
export class AppController {
constructor(@Inject("app") private readonly appService: any) { }
@Get()
getHello(): string {
return this.appService.name;//返回自定义数据中的属性值
}
}
再次访问页面localhost:3000
,页面显示如下:
4、使用工厂函数注入服务
有些场景下,我们需要根据条件动态引入服务,这时候我们可以借助useFactory
工厂函数,来动态加载。
//app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
//配置服务
const configService = {
provide: "config",
useValue: { url: "11.22.33.44" },
}
@Module({
imports: [],
controllers: [AppController],
providers: [configService, {
provide: "app",
inject: ["config"],//引入配置服务config,这样可以给useFactor作为入参使用
useFactory: (config) => {
//合并config配置服务,并且追加appUrl属性。
return {
...config,
appUrl: "itfeiniu.com"
}
}
}],
})
export class AppModule { }
//app.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
@Controller()
export class AppController {
constructor(@Inject("app") private readonly appService: any) { }
@Get()
getHello(): string {
//appService服务实例中,使用useFactory引入时,混合了config和自定义追加的appUrl。这里都可以调用。
return "url:" + this.appService.url + " <br/> appUrl:" + this.appService.appUrl;
}
}
使用工厂函数来动态加载服务时,我们可以使用injest
引入其他服务,然后就可以在工厂函数中写业务逻辑,返回我们需要的服务数据。上述例子中,app
服务最终返回的是:
{
url: "11.22.33.44",
appUrl: "itfeiniu.com"
}
所有我们在控制器中可以直接调用。
工厂函数常见用于,根据环境变量的不同,动态引入不同环境下对应的服务数据。
5、异步引入服务
服务在注册的时候,我们也可以使用异步引入,useFactory
工厂函数返回一个Promise
对象。具体如下:
//app.module.ts
@Module({
imports: [],
controllers: [AppController],
providers: [configService, {
provide: "app",
inject: ["config"],
useFactory: async (config) => {
//返回一个Promise对象
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
...config,
appUrl: "itfeiniu.com"
});
}, 3000);
})
}
}],
})
通过后端服务运行日志,可以看到,服务加载耗时3s
。
6、共享服务
一个服务,我们有时候需要在不同的模块中使用,那么就需要使用服务共享。
-
创建一个
test
服务,执行如下代码,创建test.module.ts
和test.service.ts
文件:nest g mo test nest g s test --no-spec
创建后,会自动引入
app.module.ts
-
test.service.ts
代码import { Injectable } from '@nestjs/common'; @Injectable() export class TestService { constructor() { } getTest(): string { return 'Hello Test!'; } }
-
test.module.ts
代码import { Module } from '@nestjs/common'; import { TestService } from './test.service'; @Module({ providers: [TestService], exports: [TestService]//不光要在app.module.ts的import中引入,还需要在这里导出 }) export class TestModule { }
-
app.module.ts
代码:@Module({ imports: [TestModule],//使用nest g s test命令行创建后,会自动添加;否则需要手动添加。 controllers: [AppController], ... })
-
app.controller.ts
代码:import { Controller, Get, Inject } from '@nestjs/common'; import { TestService } from './test/test.service'; @Controller() export class AppController { constructor(private testService:TestService) { } @Get() getHello(): string { return this.testService.getTest();//调用test服务中的getTest方法,返回:Hello Test! } }
配置完上述代码,我们再次访问:localhost:3000
页面如下:
三、支持环境变量
1、使用dotenv
-
安装
pnpm add dotenv
。 -
根目录下创建
.env
文件,内容如下:
NODE_ENV=development
- 使用
import {config} from "dotenv";
const path = require('path');
config({path:path.join(__dirname,'../.env')});
console.log(process.env.NODE_ENV);//读取
2、使用corss-env
-
全局安装
cross-env
,执行:pnpm add cross-env -g
-
调整
package.json
中的script
配置:
"scripts": {
"start:dev": "cross-env APIHOST=itfeiniu.com nest start --watch",
...
},
- 如何使用:
process.env.APIHOST
以上两种环境变量的方式,可以同时存在。
不用的import对象,可以使用
shift+alt+o
自动整理清除。