NestJs入门篇(一)

NestJS是一个用于构建高效、可扩展的服务器端应用程序的Node.js框架。它基于现代JavaScript(或TypeScript)构建,并采用了面向对象的编程(OOP)、函数式编程(FP)和响应式编程(RP)的最佳实践。

NestJS的特点

  1. 基于模块化:NestJS使用模块化的方式组织代码,使得应用程序的各个部分可以独立开发、测试和维护。模块化的结构也使得应用程序更加可扩展和可维护。

  2. 强大的依赖注入(DI):NestJS内置了依赖注入容器,使得开发者可以轻松管理应用程序的各个组件之间的依赖关系。依赖注入提高了代码的可测试性和可重用性。

  3. 支持多种服务器端技术:NestJS可以与多种服务器端技术(如Express、Fastify等)无缝集成,开发者可以选择适合自己项目需求的技术栈。

  4. 强大的路由和中间件支持:NestJS提供了灵活的路由和中间件机制,使得开发者可以轻松地定义和管理应用程序的路由和中间件。

  5. 支持WebSocket:NestJS内置了对WebSocket的支持,使得开发者可以轻松地构建实时应用程序,如聊天应用、实时通知等。

  6. 完善的文档和社区支持:NestJS拥有完善的文档和活跃的社区支持,开发者可以轻松地找到所需的帮助和资源。

命令行安装

我们首先安装NestJs提供的脚手架 Nest Climac os 或者linux下打开terminal 在Windows下打卡CMD命令行或者wsl执行下面的node 命令:

shell 复制代码
npm i -g @nestjs/cli

第一个入门程序

安装好脚手架之后我们执行:

shell 复制代码
nest new project-name

选择一个包管理工具进行安装比如pnpm出现下面的字样则表示项目已经初始化完成。

找一个喜欢的IDE打开刚创建好的项目。

简单看下目录结构:

  • node_modules 项目所需的依赖

  • src源码目录 比如nestJs的启动文件main.ts

  • test 测试文件目录

在终端执行:

shell 复制代码
pnpm run start:dev

运行刚才创建的项目, 在浏览器打开localhost:3000

返回hello world 则我们项目已经成功运行起来了。

打开我们创建好的项目看下他是怎么返回hello world 字符串的。

打开src下的app.controller.ts

typescript 复制代码
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();
  }
}

可以看到在AppController类上有一个@Controller装饰器,简单来说这个controller装饰器是用来匹配访问路径的,现在没写path默认的是\

类里面一个getHello的方法返回了一个字符串,它上面也标记了一个函数装饰器,表示这个方法的请求方式是get,我们在浏览器打开localhost:3000 打开调试工具,选择NetWork然后选择All选项卡。

就可以看到请求路径和请求方式。按住shift或者command点击鼠标左键打开app.service.ts这个文件。

ts 复制代码
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

这个类里面定义了一个getHello的方法,返回了一个Helloowrld字符串。这个类上面同样也有一个装饰器Injectable ,表示这个类可以被注入的其他地方。

在回到app.controller.ts文件里,我们发现在AppController这个类的构造器里面注入了 AppService这个类。

ts 复制代码
constructor(private readonly appService: AppService) {}

但是仅仅这样还是不能在AppController里面使用。我们打开app.module.ts

ts 复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

在这个文件里面通过@Module的providers声明了AppService,这样就可以在controller里面使用service类提供的方法,并且不用手动的去管理service类的初始化,这个就是依赖注入简称DI。全流程如下

到此,第一个入门程序就写完了。

HTTP请求方式

在入门程序中我们已经写了一个不带参数的get请求,但是在实际开发中很少有不带参数的情况,所以我们还要学习下,怎么解析http请求携带的参数。

常见的数据传输方式有以下几种:

  • url param

  • query

  • form-urlencoded

  • form-data

  • json

url param

我们可以把参数写在url中,比如:

http 复制代码
http://localhost:3000/course/detail/9527

9527就是路径参数,服务端框架或者SPA都支持从URL中取出参数。

query

查询参数是通过url中的?后面用&分割的字符串传递数据,比如:

http 复制代码
http://localhost:3000/order?id=9527&page=1&pageSize=10

id ,page ,pageSize就是query中的参数。使用这种方式传递参数时,对于一些特殊字符要进行编码之后再传递(encodeURIComponent)。

form-urlencoded

直接使用form表单提交的数据就是这种,和查询字符串的区别只是放在了body里,指定content-type 是application/x-www-form-urlencoded。因为内容也是query字符串所以对特殊字符也要进行编码。

通过 & 分隔的 form-urlencoded 的方式需要对内容做 url encode,如果传递大量的数据,比如上传文件的时候就不是很合适了,因为文件 encode 一遍的话太慢了,这时候就可以用 form-data。

form-data

form data 不再是通过 & 分隔数据,而是用 --------- + 一串数字做为 boundary 分隔符。因为不是 url 的方式了,所以也不用再做 url encode。

form-data 需要指定 content type 为 multipart/form-data,然后指定 boundary 也就是分割线。

JSON

form-urlencoded 需要对内容做 url encode,而 form data 则需要加很长的 boundary,两种方式都有一些缺点。如果只是传输 json 数据的话,不需要用这两种。

可以直接指定content type 为 application/json 就行。

我们平时传输 json 数据基本用的是这种。

简单实现

下面我们就用nestJs来实现下。我们用NestJs的cli创建一个新的module:

shell 复制代码
nest g resource params-parse

然后选择REST API快速生成一个带crud的接口:

我们只在controller文件里面编写我们的参数解析代码:

1. url params

url params是url中的参数,NestJs里面通过:参数名的方式来声明,然后通过@Param装饰器取出来。

编写解析url params的代码:

ts 复制代码
import { Controller, Get, Param } from '@nestjs/common';

@Controller('params-parse')
export class ParamsParseController {
  @Get(':id')
  urlParma(@Param('id') id: string) {
    return `传递的id:${id}`;
  }
}

打开postman新建一个collection, 在collection里面创建一个get请求。 输入请求路径:

http 复制代码
localhost:3000/params-parse/9527

点击send按钮,如下图,服务端已经拿到我们传递的参数,并进行了响应。

ts 复制代码
import { Controller, Get, Param, Query } from '@nestjs/common';

@Controller('params-parse')  
export class ParamsParseController {  
@Get('query')  
query(@Query('code') code: string) {  
return `传递的code:${code}`;  
}

@Get(':id')  
urlParma(@Param('id') id: string) {  
return `传递的id:${id}`;  
}  
}

2. query

query是url中?后的字符串,在NestJs里面通过@Query装饰器来取参数。

编写获取query的代码:

typescript 复制代码
import { Controller, Get, Param, Query } from '@nestjs/common';

@Controller('params-parse')
export class ParamsParseController {
  @Get('query')
  query(@Query('code') code: string) {
    return `传递的code:${code}`;
  }

  @Get(':id')
  urlParma(@Param('id') id: string) {
    return `传递的id:${id}`;
  }

tips:

query的路由要放到:id路由的前面,nest是从上到下匹配的,如果放在后面就匹配到了:id。

同样我们在postman里面创建一个请求,如下图:

服务端成功接收到了我们的请求。

3. form urlencoded

form urlencode是通过body传输数据,用NestJs解析的话,使用@Body装饰器,NestJs会解析请求体,然后注入到dto中(dto是用于封装传输的数据对象)。

编写获取解析body的代码:

ts 复制代码
export class CreateParamsParseDto {
  code: string;
}


import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
import { CreateParamsParseDto } from './dto/create-params-parse.dto';

@Controller('params-parse')
export class ParamsParseController {
  @Get('query')
  query(@Query('code') code: string) {
    return `传递的code:${code}`;
  }
  @Post()
  body(@Body() createParamsParseDto: CreateParamsParseDto) {
    return `传递的对象:${JSON.stringify(createParamsParseDto)}`;
  }
  @Get(':id')
  urlParma(@Param('id') id: string) {
    return `传递的id:${id}`;
  }
}

同样在postman里面创建一个post方式的请求:

4. form data

form data 是用------作为boundary分割传输的内容。

NestJs解析form data 使用FilesInterceptor的拦截器,用@UseInterceptors装饰器启用,通过UploadFiles来取。非文本的内容,同样是通过@Body来解析。

解析文件对象时,要安装multer的类型定义文件:

shell 复制代码
pnpm i @types/multer

编写获取解析body的代码:

ts 复制代码
import {
  Body,
  Controller,
  Get,
  Logger,
  Param,
  Post,
  Query,
  UploadedFiles,
  UseInterceptors,
} from '@nestjs/common';
import { CreateParamsParseDto } from './dto/create-params-parse.dto';
import { AnyFilesInterceptor } from '@nestjs/platform-express';
import { Express } from 'express';

@Controller('params-parse')
export class ParamsParseController {

  @Post('file')
  @UseInterceptors(AnyFilesInterceptor({ dest: 'uploads/' }))
  file(
    @Body() createParamsParseDto: CreateParamsParseDto,
    @UploadedFiles() files: Array<Express.Multer.File>,
  ) {
    Logger.log(files);
    return `传递的对象:${JSON.stringify(createParamsParseDto)}`;
      }
  }

同样我们在postman里面创建一条post请求测试下:

上传的文件目录:

5. json

json是我们现在用的最多的数据请求格式,设置content-type为application/json。

NestJs解析时依然会使用@Body来接收,不需要改动。

增加一条解析body的请求方法:

ts 复制代码
import {
  Body,
  Controller,
  Get,
  Logger,
  Param,
  Post,
  Query,
  UploadedFiles,
  UseInterceptors,
} from '@nestjs/common';
import { CreateParamsParseDto } from './dto/create-params-parse.dto';
import { AnyFilesInterceptor } from '@nestjs/platform-express';
import { Express } from 'express';

@Controller('params-parse')
export class ParamsParseController {
  @Post('json')
  json(@Body() createParamsParseDto: CreateParamsParseDto) {
    return `传递的对象:${JSON.stringify(createParamsParseDto)}`;
  }
 }

同样postman里面新建一个http请求,测试下:

Providers

由于NestJs实现了IOC容器,会从入口模块开始扫描,分析Module之间的引用关系,对象之间的依赖关系,自动把provider注入到目标对象。

NetsJs官网对providers的原文介绍如下:

Providers are a fundamental concept in Nest. Many of the basic Nest classes may be treated as a provider -- services, repositories, factories, helpers, and so on. The main idea of a provider is that it can be injected as a dependency; this means objects can create various relationships with each other, and the function of "wiring up" these objects can largely be delegated to the Nest runtime system.

前面我们提到了一个新的概念IOC容器,那么IOC到是什么呢?chatGpt给出的解释如下:

IOC(Inversion of Control,控制反转)是一种设计原则,它主要解决了以下问题:

  1. 依赖管理:在传统的编程模式中,对象之间的依赖关系通常是硬编码在代码中的,这使得对象之间的耦合度很高。当需要修改一个对象时,可能需要同时修改依赖它的其他对象,这样会增加代码的复杂性和维护成本。而IOC通过将对象的创建和依赖关系的管理交给容器来处理,降低了对象之间的耦合度,使得对象的修改和替换更加方便。
  2. 解耦合:传统的编程模式中,对象之间的依赖关系通常是紧密耦合的,这使得对象之间的交互变得困难。而IOC通过将对象的依赖关系委托给容器来管理,使得对象之间的解耦合成为可能。对象只需要关注自己的功能实现,而不需要关心依赖的对象是如何创建和管理的。
  3. 可测试性:在传统的编程模式中,对象之间的紧耦合关系会导致测试困难,因为一个对象的测试往往依赖于其他对象的存在和状态。而IOC通过依赖注入的方式,使得对象的依赖关系可以在测试时进行模拟和替换,从而提高了代码的可测试性。
  4. 可扩展性:传统的编程模式中,当需要添加新的功能或修改现有功能时,往往需要修改已有的代码,这可能会带来一系列的风险和问题。而IOC通过将对象的创建和依赖关系的管理交给容器来处理,使得新功能的添加和现有功能的修改变得更加灵活和可扩展。 总的来说,IOC通过将对象的创建和依赖关系的管理交给容器来处理,解决了传统编程模式中对象之间耦合度高、依赖关系难以管理、测试困难等问题,提高了代码的可维护性、可测试性和可扩展性。

打开我们第一个入门程序中的app.service.ts发现provider是用Injectable装饰器装饰的class对象。

ts 复制代码
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

app.module.ts的providers里面声明这个provider:

ts 复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ParamsParseModule } from './params-parse/params-parse.module';
@Module({
  imports: [ParamsParseModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

上面的是简化的写法,完整的写法如下:

ts 复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ParamsParseModule } from './params-parse/params-parse.module';
@Module({
  imports: [ParamsParseModule],
  controllers: [AppController],
  providers: [
    {
      provide: AppService,
      useClass: AppService,
    },
  ],
})
export class AppModule {}过

通过provide去指定key,通过useClass去指定注入的对象的类。NestJs会做实例化在注入。

比如通过构造器:

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();
  }
}

或者也可以通过属性注入,通过属性注入时我们要用一个新的装饰器Inject:

ts 复制代码
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  // constructor(private readonly appService: AppService) {}
  @Inject(AppService)
  private readonly appService: AppService;

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

除了对象之外,我们还可以注入字符串,在module中的providers手动指定一个provider的key:

ts 复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ParamsParseModule } from './params-parse/params-parse.module';
@Module({
  imports: [ParamsParseModule],
  controllers: [AppController],
  providers: [
    {
      provide: AppService,
      useClass: AppService,
    },
    {
      provide: 'test_string_token_inject',
      useClass: AppService,
    },
  ],
})
export class AppModule {}
ts 复制代码
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(
    private readonly appService: AppService,
    @Inject('test_string_token_inject')
    private readonly testStringTokenAppServiceInject: AppService,
  ) {}
  @Get()
  getHello(): string {
    return this.testStringTokenAppServiceInject.getHello();
  }
}

除了上面的方法NestJs还提供了其他注入的方法比如可以使用useFactory实现动态注入。

总结

在本章介绍了下NestJs并且通过NestJs Cli 创建了第一个入门程序,分析了调用链路,介绍了controllerservice 装饰器的使用。分析了Http请求最常见的5中传递数据的方式,url param,query, form-data,json,form-urlencoded ,以及他们在Nestjs中的参数解析的实现。接着介绍了ProvidersIOC容器的概念,以及常见的依赖注入方式及注入值的获取方式。

在下一章,将学习 Module,MiddleWare,Exception filters,Pipes以及Guard

相关推荐
垣宇9 小时前
Vite 和 Webpack 的区别和选择
前端·webpack·node.js
爱吃南瓜的北瓜9 小时前
npm install 卡在“sill idealTree buildDeps“
前端·npm·node.js
翻滚吧键盘9 小时前
npm使用了代理,但是代理软件已经关闭导致创建失败
前端·npm·node.js
浪九天10 小时前
node.js的版本管理
node.js
浪九天12 小时前
node.js的常用指令
node.js
浪九天14 小时前
Vue 不同大版本与 Node.js 版本匹配的详细参数
前端·vue.js·node.js
小纯洁w1 天前
Webpack 的 require.context 和 Vite 的 import.meta.glob 的详细介绍和使用
前端·webpack·node.js
熬夜不洗澡1 天前
Node.js中不支持require和import两种导入模块的混用
node.js
bubusa~>_<1 天前
解决npm install 出现error,比如:ERR_SSL_CIPHER_OPERATION_FAILED
前端·npm·node.js
天下皆白_唯我独黑1 天前
npm 安装扩展遇到证书失效解决方案
前端·npm·node.js