This website requires JavaScript.
ROSY
GRAY

觉宇宙之无穷,识盈虚之有数。

——王勃/《滕王阁序》

BING
转载

图解Nestjs - 适合中国宝宝的入门指导

共 6,060 字,需阅读 15 分钟2024/09/09 下午39 次阅读

笔者入门Nest的时候属实是迷糊了一阵,本文将从初学者的视角出发,试图为大家解释Nestjs到底是如何运作的。如有错误欢迎指出,谢谢~

image.png
image.png

假设我们来做这样一个服务:宝可梦大全

提供四个接口:

  1. 获取完整的宝可梦列表
  2. 根据宝可梦编号获取某一只宝可梦的信息
  3. 获取完整的技能列表
  4. 根据某个技能获取可以学会该技能的宝可梦列表

Module = 模块

Module,中文译作模块,我们将从它来入手,搞清楚Nestjs大概是如何工作的。

image.png
image.png

官网这张图表达的很清楚,Nest的大致理念就是一颗“模块树”,从根模块出发,连接到许多的子功能模块。

我们的宝可梦查询的结构可能是这样的:

(本文中我们使用Prisma来操作数据库)

现在让我们聚焦到其中一个功能模块:Pokemon Module

细说Pokemon Module

这个模块的领域是宝可梦,显然这是一个非常核心的模块。那么“模块”,又是怎么运作的呢?

从外部看,“模块”是一个黑盒,有自己的输入和输出:

而黑盒的内部,则主要是两部分:

  1. Services
  2. Controllers

Controllers是接收请求的入口,Services则是方法实现,这个应该不难理解

那么我们仔细看看所谓的输入输出是什么。

Module的输入是什么?

由于宝可梦的信息是存在数据库中的,因此宝可梦模块需要Prisma模块来与数据库交互。所以Prisma Module是Pokemon Module的输入。

Module的输出是什么?

既然我们说Prisma Module是Pokemon Module的输入,那,Prisma Module一定是输出了什么,对吧?

没错,Prisma Module的输出就是Prisma Service。Pokemon Module可以使用Prisma Service中的各种方法来交互数据库。

Provider?

在刚刚的叙述中,我们还没有提及在Nest中非常有存在感的Provider

我们说过,“Controllers是接收请求的入口,Services则是方法实现”,那么,Controllers是如何调用Services的呢?答案就是,当Services成为Provider时

这就像是同一样东西的两种名字。它既是Pokemon Service,也是Pokemon Module的Provider,取决于从什么视角去看他。因此,在代码中它往往表现为这样:

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
// nestjs project directory |-pokemon | |_pokemon.module.ts | |_pokemon.controller.ts | |_pokemon.service.ts |-......
              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
// pokemon.module.ts @Module({ controllers: [PokemonController], providers: [PokemonService], imports: [PrismaModule], exports: [PokemonService], })
              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
// pokemon.controller.ts export class PokemonController { // 正是因为Module的providers中传入了PokemonService // Controller的constructor才能接收到pokemonService参数 constructor(private readonly pokemonService: PokemonService) {} @Get('/find/all') async findAll() { const allPokemons = await this.pokemonService.findAll(); // ...... } @Get('/find/:id') async findOne( @Param('id', ParseIntPipe) id: number, ) { const pokemon = await this.pokemonService.findOne(id); // ...... } }
              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
// pokemon.service.ts // 此行Injectable装饰器也是必要的 // 关于Dependency Injection的更多信息,建议参考Angular官方文档:https://angular.dev/guide/di @Injectable() export class PokemonService { constructor(private prisma: PrismaService) {} parsePokemon(pokemon) { // ... } async findAll() { const pokemons = await this.prisma.pokemon.findMany({ include: { likes: true, }, }); return pokemons.map((item) => this.parsePokemon(item)); } async findOne(id: number) { const pokemon = await this.prisma.pokemon.findUnique({ where: { id } }); if (!pokemon) { throw new NotFoundException(`pokemon ${id} not found`); } return this.parsePokemon(pokemon); } }

Provider vs Import

在上面的例子我们可以看到,由于Injectable的PokemonService被放入了module定义的providers中,PokemonController便可以使用它,那么它的功能岂不是和imports重叠了?

事实上,如果我们去掉imports,将Prisma Service也放到providers中,我们的代码依然可以运行:

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
// pokemon.module.ts @Module({ controllers: [PokemonController], providers: [PokemonService, PrismaService], imports: [], exports: [PokemonService], })

直接先说结论:用Import

具体来说,只要是用外部服务(也就是除去PokemonModule将PokemonService作为Provider这种情况),使用import更为合适。

区别在于:import module会复用已经创建的实例,而每个provider都会创建新的实例。

前者占用更少的内存,且一个可复用的实例在多处被使用在需求中也更加常见。

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
// pokemon.module.ts // 推荐 @Module({ controllers: [PokemonController], providers: [PokemonService], imports: [PrismaModule], exports: [PokemonService], }) // 不推荐 @Module({ controllers: [PokemonController], providers: [PokemonService, PrismaService], imports: [], exports: [PokemonService], })

总结

看一个Nestjs应用是怎么跑的,首先就是看明白Module之间是如何互相作用的。

那么在实现我们自己的Module时,只需要记住:

  • 定义自己的controllers
  • 定义自己的service
  • 把自己的service放在providers中,这样controllers才能用service
  • 如果需要用外部的service,将外部module放在imports里
  • 如果自己的service也需要被其他module使用,将自己的service放到exports里

比如这样:

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
// pokemon.module.ts @Module({ controllers: [PokemonController], providers: [PokemonService], imports: [PrismaModule], exports: [PokemonService], }) // skill.module.ts @Module({ controllers: [SkillController], providers: [SkillService], imports: [PrismaModule, PokemonModule], })

好了,恭喜你,你已经完全掌握Nestjs了!

image.png
image.png

参考

原文链接

图解Nestjs - 适合中国宝宝的入门指导

自由转载 - 署名 - 非商业性使用https://blog.rosygray.com/article/29
0 / 0 条看法
访客身份
在下有一拙见,不知...
期待你的捷足先登