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

相关推荐
~甲壳虫3 小时前
说说webpack中常见的Plugin?解决了什么问题?
前端·webpack·node.js
~甲壳虫4 小时前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫4 小时前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
熊的猫5 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
前端青山14 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
GDAL16 小时前
npm入门教程1:npm简介
前端·npm·node.js
郑小憨1 天前
Node.js简介以及安装部署 (基础介绍 一)
java·javascript·node.js
lin-lins1 天前
模块化开发 & webpack
前端·webpack·node.js
GDAL2 天前
npm入门教程13:npm workspace功能
前端·npm·node.js
wumu_Love2 天前
npm 和 node 总结
前端·npm·node.js