Nestjs系列(一) Nestjs基础

风希落 / 2024-03-01 / 原文

快速使用 Nestjs

Nest 项目的文件层级和 JAVA 项目的层级架构较为相似。Nest 项目的层级架构统一由 Controller Module Service 三个模块组成。

  • 安装 nest cli,创建项目
npm install -g @nestjs/cli
nest new [项目名]

项目默认运行至 http://localhost:3000/ 上

  • 当 nest 版本有更新,本地 cli 若需要更新至最新版本
npm update -g @nestjs/cli

Nestjs 常用命令

nest n

nest nnest new的缩写。功能:创建项目模板。
创建项目时,使用的就是 nest new。使用 nest new -h,查看 nest new 的可选命令参数

image

其中 strict 模式对应的 5 个配置项为

{
  "compilerOptions": {
    // 若开启 strict 严格模式,则这五项为 true
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false
  }
}

nest g

nest gnest generate的缩写。功能:生成代码模板。
详细命令可通过 nest g -h 查看

image

常用命令如下

  • nest g controller my 生成控制层名为 my 的 controller 模板,简写 nest g co my
    image
  • nest g module my 生成模块层名为 my 的 moudle 模板,简写 nest g mo my
    image
  • nest g service my 生成服务层名为 my 的 service 模板,简写 nest g s my
    image
  • nest g resource crud 生成CRUD模板名为 crud 的 CRUD 模板,简写 nest g res crud,执行该命令时,可以选择多种风格的 CRUD 模板,常规选择 Rest API
    • crud 的模块相比单个创建的,多了 dto(前后端交互时的参数模板)和 entity(实体类,即数据库字段映射)
    • 同时,该命令创建的模板,其 module 也会自动注入到 app module 中
      image

nest build

nest build -h

image

其中编译打包时的不同选项模式,编译出的文件也不一样。

  • nest build -b webpack(可以直接 nest build --webpack,但该命令已过时,推荐使用 --builder [name]
    image
  • nest build -b tsc
    image

tsc 编译和 webpack 打包的不同点在,tsc 不会做打包,而 webepack 会打包成单文件。

nest-cli.json

nest 的命令选项都可以在 nest-cli.json 文件中进行配置,在命令执行时,会自动读取该文件中已有的配置选项。nest-cli 详细配置

{
  // ...
  "sourceRoot": "src",  // 指定源码目录
  "entryFile": "main",  // 入口文件,默认 main
  "compilerOptions": {
    "deleteOutDir": true, // 每次打包都会先删除 dist 目录
    // 把非 js ts 文件也打包进 dist 文件夹中,但仅支持 src 目录下的资源,非 src 不行
    "assets": [
      // 可使用 include exclude 来进行精准匹配
      { "include": "*.env", "outDir": "dist" }
    ]
  },
  "generateOptions": {
    "spec": false  // 跳过 spec 文件生成(推荐)
  }
}

nest start

常用命令可参考 package.json 中的脚本执行命令。例如 --watch 文件变动自动更新,--debug 调试 debug

nest info

查看项目信息的,系统信息、 node版本、项目依赖的包管理工具版本、nest 的依赖包版本

支持静态资源访问

参考中文官网链接:https://nest.nodejs.cn/recipes/serve-static
安装 @nestjs/serve-static,使用其提供的 ServeStaticModule

pnpm install @nestjs/serve-static

app.module.ts 中导入配置项

// ...
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';

@Module({
  imports: [
    ServeStaticModule.forRoot({
      rootPath: join(__dirname, '..', 'client'),
    }),
    // ....
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

rootPath 是指定哪个目录下为静态资源,代码中指定 client 文件夹下的为可访问的静态资源

比如在 client 文件夹下新建 index.html。执行 npm run start:dev 启动项目,访问 http://localhost:3000/client/index.html 即可看到该 html 的内容资源。
注意: 静态资源默认访问的渲染路径(renderPath)就是 /index.html ,比如在 client 文件加下新建 my.html 是访问不到的。

Nest和前端的多种参数类型传输和接收

此处可以新建一个 nest 项目,使用命令快速创建 CRUD 模板,启动项目访问 http://localhost:3000/person/110地址,页面出现 This action returns a #110 person 即为成功。

url 参数

拼接到请求路径的动态参数

@Controller('person')
export class PersonController {
  @Get(':id')
  getUrlParam(@Param('id') id: string) {
    return `urlId=${id}`;
  }
}
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
<script>
  async function getUrlParam() {
    const res = await axios.get('/person/110');
    console.log(res);
  }
  getUrlParam();
</script>

image

query 参数

多个请求参数以&的方式拼接到 url 上。例如 /api/list?current=1&pageSize=10&name=%E5%85%B3%E9%97%AD,axios 会把中文 encodeURIComponent 转换

@Get('list')
getQuery(
  @Query('current') current: string,
  @Query('pageSize') pageSize: string,
  @Query('name') name: string,
) {
  return { name, current, pageSize };
}

@Get(':id')
getUrlParam(@Param('id') id: string) {
  return `urlId=${id}`;
}
const res = await axios.get('/person/list?name=张三&current=1&pageSize=10',);
console.log(res);

image

注意事项

当 url param 和普通请求地址一起使用时,尽量将 url param 的方法写在最下边,原因是请求时,Nest 自上而下匹配路由,当匹配到动态路由时,可以匹配到,就不再执行后续的路由匹配了。

错误写法示范:

image

body 参数

参数在请求体中,通常用于 post put path 等。

在创建 CRUD 模板时,Nest 贴心的准备了两个 dto 文件(前后端请求体参数实体),在 dto 文件夹下。
Nest 接收时,使用装饰器 @Body,标识为请求体,Nest 会自动解析,和 java 的 Spring 使用相似。

export class CreatePersonDto {
  name: string;
  age: number;
}
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
import { CreatePersonDto } from './dto/create-person.dto';

@Controller('person')
export class PersonController {
	// ...
	@Post()
	addBody(@Body() createDto: CreatePersonDto) {
	  return createDto;
	}
}
const res = await axios.post('/person', {
  name: '张三',
  age: 20,
});
console.log(res);

image

form-data 文件上传

具体可参考 Nest中文网
安装文件上传的 TS 类型包

pnpm i @types/multer -D
  • FileInterceptor 拦截上传接口,接受两个参数,filename 和 配置项 options(以如下代码举例)
    • 第一个参数是只允许字段名称是 myfile 的上传接口访问,其余不予通过。
    • dest 就是接收时在根目录存放资源的目录名称,项目运行/编译时会自动创建。
  • AnyFilesInterceptor 允许任何字段名称的文件访问,不做限制,调用接口即可响应
  @Post('upload')
  @UseInterceptors(FileInterceptor('myfile', { dest: 'upload' }))
  // @UseInterceptors(AnyFilesInterceptor({ dest: 'upload' }))
  uploadFile(@UploadedFile() file: Express.Multer.File) {
    console.log(file);
    return {
      filename: file.filename,
      originalname: file.originalname,
      msg: '上传成功',
    };
  }

注意: 当使用 AnyFilesInterceptor 时,可以上传成功,但是读取不到 file 数据,为 undefined。

<body>
  <input id="fileControll" type="file" />
</body>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
<script>
  const fileInput = document.querySelector('#fileControll');
  fileInput.addEventListener('change', async (event) => {
    const file = event.target.files[0];
    const data = new FormData();
    data.append('myfile', file);

    const res = await axios.post('/person/upload', data);
    console.log(res);
  });
</script>

image

IOC 控制反转

IOC 对于前端框架来说解释起来无异于纸上谈兵,极为抽象,在介绍 IOC 时,会结合 Nest 和 JAVA 的 Spring 框架来进行解释。

Nest 不使用 IOC 产生的问题

各个模块之间存在着复杂的依赖关系,比如如下内容

  • controller 依赖了 service
  • service 依赖了 repository
  • repository 依赖了 dataSource
  • dataSource 依赖了 config
const config = new Config({ username: 'xxx', password: 'xxx'});

const dataSource = new DataSource(config);

const repository = new Repository(dataSource);

const service = new Service(repository);

const controller = new Controller(service);

这会导致一个问题,必须先创建谁,然后才能根据依赖再找下一个需要创建谁。在项目初始化时,必须要保证每个模块的加载顺序,还要保证不会出现多余创建的模块,是非常麻烦的。同时,也违背了单例设计模式。

了解 Spring 中的 IOC

一个相对完整的 Spring 项目大概有 Controller Mapper Service。更多还会有 impl 实现类等等。

image

在这里,Controller 依赖 Service,Service 依赖 Mapper,Mapper 则去查询 SQL,(实际上查询 SQL 也需要配置和依赖数据库)。

在 java 中,全都是面向对象编程的 class,而在依赖使用/调用时,并没有做 new 的动作,而是使用 @Autowired,这个注解可以自动装配模块,在调用时,会自动加载访问模块内的方法,无需担心加载顺序和执行问题。

Nest 中的 IOC

创建一个新项目,快速生成两个 crud 模块,personanimal