无题 匡思进 2025-11-29 2025-11-29 什么是Nest Nest (NestJS) 是一个用于构建高效、可扩展 Node.js 服务器端应用程序的框架。它使用渐进式 JavaScript,使用 TypeScript 构建并完全支持(但仍允许开发人员使用纯 JavaScript 进行编码),并结合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数式响应式编程)的元素。
Nestjs框架基于Express和Fastify(速度最快的web框架之一)
开始 启动
开发的时候需要监视文件的变化,自动重新编译和重新加载服务器
文件介绍 1 2 3 4 5 6 7 8 9 10 - node_modules — 放置项目的依赖文件 - src — 放置开发的代码文件 - app.controller.spec.ts — 测试 app.controller.ts 文件的单元测试文件 - app.controller.ts — 根控制器文件,处理路由的请求和定义路由处理器 - app.module.ts — 根模块文件,Nest 应用的主要入口 - app.service.ts — 根服务文件,用于处理根控制器中的业务逻辑 - main.ts — 应用程序的入口文件,用于初始化和启动 Nest 应用 - test — 测试文件,用于编写应用程序的单元测试和集成测试 - app.e2e-spec.ts — 编写和运行 E2E 测试的测试规范文件 - jest-e2e.json —Jest 的 E2E 测试配置文件
项目划分结构
1 2 3 4 5 6 7 8 9 10 11 12 13 - src - core - common - middleware - interceptors - guards - user - interceptors (scoped interceptors) - user.controller.ts - user.model.ts - store - store.controller.ts - store.model.ts
tsconfig.json
1 2 3 4 "experimentalDecorators":true,启动实验性的装饰器特性 "target":"ES2821",指定ECMAScript目标版本 "moduleResolution":"NodeNext",如何查找第三方模块 "module":"NodeNext", 指定生成的模块代码系统
创建新的资源模块
一般会自动在 app.module.ts 文件中引入
IOC控制反转 和 DI 依赖注入 [[spring IOC和DI 理解_springioc和di的一个理解]]
装饰器 使用装饰器前需要确保在 tsconfig,json 中包含以下配置
1 2 3 4 5 { "compilerOptions": { "experimentalDecorators": true } }
[[TypeScript 装饰器什么是装饰器? 装饰器怎么分类的? 什么是普通装饰器和装饰器工厂? 各种类编装饰器]]
Module @Module是一个装饰器,用于定义模块 模块是组织代码的基本单元,它将相关的组件(控制器、服务器、提供者 )组合在一起 Nest的模块系统是受Angular启动
Controller @Controller也是一个装饰器,用于定义控制器 控制器是处理传入HTTP清求的核心组件,每个控制器负责处理特定的请求路径和对应的HTTP方法在控制器的内部会使用路由装饰器 , 如 @Get , @Post 来定义路径和请求处理的方法
Get 路由装饰器, 用于将控制器的方法 (例如getHello) 映射到 HTTP 的 GET 请求 指定方法处理特定路径上的 GET请求
控制器
装饰器
说明
$\text{@Request()}$
请求参数
$\text{@Response()}$
响应参数
$\text{@Query}$
$\text{Get}$ 请求直接获取参数
$\text{@Body}$
$\text{Post}$ 请求直接获取参数
$\text{@Param}$
动态路由获取参数
$\text{@Headers}$
获取 $\text{header}$ 信息
$\text{@HttpCode}$
控制器返回的状态码
$\text{@Next()}$
继续处理下一个中间件或路由
Get
可以从服务器获取资源。
参数暴露在 URL 中(Query ),存在安全隐患。(参数会附加在 URL 的末尾,多个参数之间用 & 符号分隔)
受到 URL 长度的限制,无法传输大量数据。
Get 请求具有幂等性,多次请求得到的结果是相同的。
响应结果可以被浏览器缓存。
Post Post 请求是 HTTP 协议中的一种请求方法,通常用于向服务器提交数据,或者创建新的资源。使用 Post 请求时,数据会放在请求体(body)中传输,而不是暴露在 URL 里。
可以向服务器提交数据,或者创建新的资源。 参数在请求体中传输,较为安全和隐蔽。 请求体的大小没有限制,可以传输大量数据。 Post 请求默认不具有幂等性,多次请求可能得到不同结果。 响应结果默认不会被浏览器缓存。
模块 生成新的模块
命令 (生成 controller, module, service)
1 2 3 nest g co 模块名称 nest g mo 模块名称 nest g s 模块名称
生成一个完整的模块
版本控制 在 main.ts 里引入
1 import { VersioningType } from '@nestjs/common';
同时添加
1 2 3 app.enableVersioning({ type: VersioningType.URI, });
之后可以在装饰器Controller里书写版本号
1 2 3 4 @Constroller({ path: 'user', version: '1', })
或者单个请求控制
Code码规范 200 OK 304 Not Modified协商缓存了 400 Bad Request参数错误 401 Unauthorized token错误 403 Forbidden referer origin验证失败 404 Not Found接口不存在 500 Internal Server Error服务端错误 502 Bad Gateway上游接口有问题或者服务器问题
上传文件 1.主要会用到两个包 multer @nestjs/platform-express nestJs自带了
multer @types/multer 这两个需要安装
在upload Module 使用MulterModule register注册存放图片的目录
需要用到 multer 的 diskStorage 设置存放目录 extname 用来读取文件后缀 filename给文件重新命名
![[前端了解/Nest/_resources/nest/79208d1db97c771d81a3344d9ec3bd58_MD5.png]]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { Module } from '@nestjs/common'; import { UploadService } from './upload.service'; import { UploadController } from './upload.controller'; import { MulterModule } from '@nestjs/platform-express' import {diskStorage} from 'multer' import { extname,join } from 'path'; @Module({ imports: [MulterModule.register({ storage:diskStorage({ destination:join(__dirname,"../images"), filename:(_,file,callback) => { const fileName = `${new Date().getTime() + extname(file.originalname)}` return callback(null,fileName) } }) })], controllers: [UploadController], providers: [UploadService] }) export class UploadModule { }
2.controller 使用 使用 UseInterceptors 装饰器 FileInterceptor是单个 读取字段名称 FilesInterceptor是多个
参数 使用 UploadedFile 装饰器接受file 文件
![[前端了解/Nest/_resources/nest/5bbde31f02f92860ea9be98391cd721a_MD5.png]]
1 2 3 4 5 6 7 8 9 10 11 12 13 import { Controller, Get, Post, Body, Patch, Param, Delete,UseInterceptors,UploadedFile } from '@nestjs/common'; import { UploadService } from './upload.service'; import {FileInterceptor} from '@nestjs/platform-express' @Controller('upload') export class UploadController { constructor(private readonly uploadService: UploadService) {} @Post('album') @UseInterceptors(FileInterceptor('file')) upload (@UploadedFile() file) { console.log(file) return true } }
3.生成静态目录访问上传之后的图片 useStaticAssets prefix 是虚拟前缀
1 2 3 4 5 6 7 8 9 10 11 12 import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import {NestExpressApplication} from '@nestjs/platform-express' import { join } from 'path' async function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); app.useStaticAssets(join(__dirname,'images'),{ prefix:"/xiaoman" }) await app.listen(3000); } bootstrap();
下载文件 1.download 直接下载 这个文件信息应该存数据库 我们这儿演示就写死 了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import { Controller , Post , UseInterceptors , UploadedFile , Get , Res } from '@nestjs/common' ;import { UploadService } from './upload.service' ;import { FileInterceptor , FilesInterceptor } from '@nestjs/platform-express' import type { Response } from 'express' import {join} from 'path' @Controller ('upload' )export class UploadController { constructor (private readonly uploadService : UploadService ) { } @Post ('album' ) @UseInterceptors (FileInterceptor ('file' )) upload (@UploadedFile () file ) { console .log (file, 'file' ) return '峰峰35岁憋不住了' } @Get ('export' ) downLoad (@Res () res : Response ) { const url = join (__dirname,'../images/1662894316133.png' ) res.download (url) } }
2.使用文件流的方式下载 可以使用compressing把他压缩成一个zip包
import {zip} from ‘compressing’
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Get ('stream' )async down (@Res () res :Response ) { const url = join (__dirname,'../images/1662894316133.png' ) const tarStream = new zip.Stream () await tarStream.addEntry (url) res.setHeader ('Content-Type' , 'application/octet-stream' ); res.setHeader ( 'Content-Disposition' , `attachment; filename=xiaoman` , ); tarStream.pipe (res) }
前端接受流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const useFetch = async (url: string ) => { const res = await fetch (url).then (res => res.arrayBuffer ()) console .log (res) const a = document .createElement ('a' ) a.href = URL .createObjectURL (new Blob ([res],{ })) a.download = 'xiaman.zip' a.click () } const download = ( ) => { useFetch ('http://localhost:3000/upload/stream' ) }
拦截器 拦截器的核心原理:Observable 流 Nest 的 Interceptor 基于 RxJS,核心是通过 Observable 流来拦截流程。
接口的执行结果会包装成一个“数据流”(Observable),Interceptor 可以拦截这个流,加工后再传递下去
比如接口返回{ id: 1, name: "张三" },这个结果会编程一个 Observable 流,Interceptor 可以把流里的数据改成{ success: true, data: { id: 1, name: "张三" }},再传给客户端
核心方法是intercept,它接受两个参数:
context:执行上下文(和 Guard、Filter 里的类似,能拿到请求/响应对象)
next: “下一个”步骤的引用,调用next.handle()就能触发接口执行,返回的是 Observable 流
返回值:加工后的 Observable 流,必须返回流,否则客户端拿不到响应
响应拦截器 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import type { ExecutionContext, NestInterceptor, CallHandler, } from '@nestjs/common'; import type { Observable } from 'rxjs'; import { map } from 'rxjs'; import { Injectable } from '@nestjs/common'; export class ResponseIntercept implements NestInterceptor { //调用 next 处理器, 执行里面的handle()方法, 使用过 pipe 操作符进行管道处理, map操作符映射数据 intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( map((data) => { return { code: 0, data, }; }), ); } }
异常拦截器
用于全局处理HTTP异常的中间件,提供了一种统一的方式来处理和修改从后端返回给前端的错误
例如统一错误格式、向客户端发送适当的错误响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import type { ExceptionFilter, ArgumentsHost } from '@nestjs/common'; import { Catch, HttpException } from '@nestjs/common'; @Catch(HttpException) export class AbnormalFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { //获取请求上下文 const ctx = host.switchToHttp(); //上下文的response对象 const response = ctx.getResponse(); //获取异常的状态码 const status = exception.getStatus(); //异常的消息 const message = exception.message ? exception.message : 'service error'; response.status(status).json({ code: -1, data: null, msg: message, }); } }
启用
1 2 3 4 5 6 7 8 9 10 import { ResponseIntercept } from './common/responseintercept'; import { AbnormalFilter } from './common/abnormalFilter'; async function bootstrap() { app.useGlobalInterceptors(new ResponseIntercept()); app.useGlobalFilters(new AbnormalFilter()); }
Pipe 使用 main.ts
1 2 3 4 5 6 import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); app.useGlobalPipes(new ValidationPipe());
constroller.ts
1 2 3 4 5 @Post() register(@Body() req: RegisterDto) { console.log(req); return req; }
定义管道
1 2 3 4 5 6 7 8 9 10 11 12 import { IsString, Length } from 'class-validator'; export class RegisterDto { @IsString() name: string; @IsString() @Length(11) phone: string; @IsString() password: string; }
链接数据库 1 pnpm i typeorm mysql2 @nestjs/typeorm
在根目录下面创建 config.ts文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // Config.ts / ... import type { TypeOrmModuleOptions } from '@nestjs/typeorm'; export const typeOrmConfig: TypeOrmModuleOptions = { type: 'mysql', host: '127.0.0.1', port: 3306, username: 'root', password: '密码', database: 'nest-mysql', entities: ['dist/**/*.entity.{ts,js}'], // 注意:此处可能略有模糊,但格式一般如此 autoloadEntities: true, synchronize: true, };
在 app.module.ts 中添加
1 2 3 4 5 6 7 8 import { typeOrmConfig } from './config'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ TypeOrmModule.forRoot(typeOrmConfig), ], })
创建变量 在项目的 entities下添加文件 例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number = 0; @Column() name: string = ''; @Column() phone: string = ''; @Column() password: string = ''; }
在 相关模块的module.ts下面引入
1 2 3 4 5 6 import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([User])], })
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import { User } from './entities/user.entity'; import { Repository } from 'typeorm'; @Controller('user') export class UserController { constructor( @InjectRepository(User) private readonly userRespository: Repository<User>, ) {} @Post() register(@Body() req: RegisterDto) { //删除 this.userRespository.delete({ name: 'k', phone: '01299', password: '99' }); //增加 this.userRespository.save({ name: 'k', phone: '01299', password: '99' }); //更新 this.userRespository.update( { name: 'k' }, { name: 'sj', phone: '01299', password: '99' }, ); return req; } }
配置跨域 1 2 3 4 5 async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableCors(); await app.listen(process.env.PORT ?? 3000); }