NestJs入门篇(一)

2023年 9月 25日 50.5k 0

v2-5d264f5e691fab43b3b5f55711f93db0_1440w

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

NestJS的特点

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

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

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

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

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

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

  • 命令行安装

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

    npm i -g @nestjs/cli
    

    第一个入门程序

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

    nest new project-name
    

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

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

    简单看下目录结构:

    • node_modules 项目所需的依赖

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

    • test 测试文件目录

    在终端执行:

    pnpm run start:dev
    

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

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

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

    打开src下的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();
      }
    }
    

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

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

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

    import { Injectable } from '@nestjs/common';
    
    @Injectable()
    export class AppService {
      getHello(): string {
        return 'Hello World!';
      }
    }
    

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

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

    constructor(private readonly appService: AppService) {}
    

    但是仅仅这样还是不能在AppController里面使用。我们打开app.module.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://localhost:3000/course/detail/9527
    

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

    query

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

    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:

    nest g resource params-parse
    

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

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

    1. url params

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

    编写解析url params的代码:

    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请求。 输入请求路径:

    localhost:3000/params-parse/9527
    

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

    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的代码:

    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的代码:

    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的类型定义文件:

    pnpm i @types/multer
    

    编写获取解析body的代码:

    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,
      ) {
        Logger.log(files);
        return `传递的对象:${JSON.stringify(createParamsParseDto)}`;
          }
      }
    

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

    上传的文件目录:

    5. json

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

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

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

    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,控制反转)是一种设计原则,它主要解决了以下问题:

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

    import { Injectable } from '@nestjs/common';
    
    @Injectable()
    export class AppService {
      getHello(): string {
        return 'Hello World!';
      }
    }
    

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

    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 {}
    

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

    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会做实例化在注入。

    比如通过构造器:

    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:

    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:

    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 {}
    
    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 创建了第一个入门程序,分析了调用链路,介绍了controller及service装饰器的使用。分析了Http请求最常见的5中传递数据的方式,url param,query, form-data,json,form-urlencoded,以及他们在Nestjs中的参数解析的实现。接着介绍了Providers,IOC容器的概念,以及常见的依赖注入方式及注入值的获取方式。

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

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论