在开始学习之前,我们首先准备下开发环境:
- 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自动整理清除。


















