NestJS是一个用于构建高效、可扩展的服务器端应用程序的Node.js框架。它基于现代JavaScript(或TypeScript)构建,并采用了面向对象的编程(OOP)、函数式编程(FP)和响应式编程(RP)的最佳实践。
NestJS的特点
-
基于模块化:NestJS使用模块化的方式组织代码,使得应用程序的各个部分可以独立开发、测试和维护。模块化的结构也使得应用程序更加可扩展和可维护。
-
强大的依赖注入(DI):NestJS内置了依赖注入容器,使得开发者可以轻松管理应用程序的各个组件之间的依赖关系。依赖注入提高了代码的可测试性和可重用性。
-
支持多种服务器端技术:NestJS可以与多种服务器端技术(如Express、Fastify等)无缝集成,开发者可以选择适合自己项目需求的技术栈。
-
强大的路由和中间件支持:NestJS提供了灵活的路由和中间件机制,使得开发者可以轻松地定义和管理应用程序的路由和中间件。
-
支持WebSocket:NestJS内置了对WebSocket的支持,使得开发者可以轻松地构建实时应用程序,如聊天应用、实时通知等。
-
完善的文档和社区支持:NestJS拥有完善的文档和活跃的社区支持,开发者可以轻松地找到所需的帮助和资源。
命令行安装
我们首先安装NestJs提供的脚手架 Nest Cli
在mac 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,控制反转)是一种设计原则,它主要解决了以下问题:
- 依赖管理:在传统的编程模式中,对象之间的依赖关系通常是硬编码在代码中的,这使得对象之间的耦合度很高。当需要修改一个对象时,可能需要同时修改依赖它的其他对象,这样会增加代码的复杂性和维护成本。而IOC通过将对象的创建和依赖关系的管理交给容器来处理,降低了对象之间的耦合度,使得对象的修改和替换更加方便。
- 解耦合:传统的编程模式中,对象之间的依赖关系通常是紧密耦合的,这使得对象之间的交互变得困难。而IOC通过将对象的依赖关系委托给容器来管理,使得对象之间的解耦合成为可能。对象只需要关注自己的功能实现,而不需要关心依赖的对象是如何创建和管理的。
- 可测试性:在传统的编程模式中,对象之间的紧耦合关系会导致测试困难,因为一个对象的测试往往依赖于其他对象的存在和状态。而IOC通过依赖注入的方式,使得对象的依赖关系可以在测试时进行模拟和替换,从而提高了代码的可测试性。
- 可扩展性:传统的编程模式中,当需要添加新的功能或修改现有功能时,往往需要修改已有的代码,这可能会带来一系列的风险和问题。而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 创建了第一个入门程序,分析了调用链路,介绍了controller 及service 装饰器的使用。分析了Http请求最常见的5中传递数据的方式,url param,query, form-data,json,form-urlencoded ,以及他们在Nestjs中的参数解析的实现。接着介绍了Providers ,IOC容器的概念,以及常见的依赖注入方式及注入值的获取方式。
在下一章,将学习 Module
,MiddleWare
,Exception filters
,Pipes
以及Guard
。