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

原文发布于我的个人博客,欢迎来访!


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


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

提供四个接口:

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

Module = 模块

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

官网这张图表达的很清楚,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,取决于从什么视角去看他。因此,在代码中它往往表现为这样:

arduino 复制代码
// nestjs project directory
|-pokemon
| |_pokemon.module.ts
| |_pokemon.controller.ts
| |_pokemon.service.ts
|-......
typescript 复制代码
// pokemon.module.ts
@Module({
  controllers: [PokemonController],
  providers: [PokemonService],
  imports: [PrismaModule],
  exports: [PokemonService],
})
typescript 复制代码
// 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);
		// ......
  }
}
typescript 复制代码
// 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中,我们的代码依然可以运行:

typescript 复制代码
// pokemon.module.ts
@Module({
  controllers: [PokemonController],
  providers: [PokemonService, PrismaService],
  imports: [],
  exports: [PokemonService],
})

直接先说结论:用Import

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

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

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

typescript 复制代码
// 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里

比如这样:

typescript 复制代码
// pokemon.module.ts
@Module({
  controllers: [PokemonController],
  providers: [PokemonService],
  imports: [PrismaModule],
  exports: [PokemonService],
})

// skill.module.ts
@Module({
  controllers: [SkillController],
  providers: [SkillService],
  imports: [PrismaModule, PokemonModule],
})

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

参考

相关推荐
why1512 小时前
腾讯(QQ浏览器)后端开发
开发语言·后端·golang
浪裡遊2 小时前
跨域问题(Cross-Origin Problem)
linux·前端·vue.js·后端·https·sprint
声声codeGrandMaster2 小时前
django之优化分页功能(利用参数共存及封装来实现)
数据库·后端·python·django
呼Lu噜2 小时前
WPF-遵循MVVM框架创建图表的显示【保姆级】
前端·后端·wpf
bing_1582 小时前
为什么选择 Spring Boot? 它是如何简化单个微服务的创建、配置和部署的?
spring boot·后端·微服务
学c真好玩3 小时前
Django创建的应用目录详细解释以及如何操作数据库自动创建表
后端·python·django
Asthenia04123 小时前
GenericObjectPool——重用你的对象
后端
Piper蛋窝3 小时前
Go 1.18 相比 Go 1.17 有哪些值得注意的改动?
后端
excel3 小时前
招幕技术人员
前端·javascript·后端
盖世英雄酱581363 小时前
什么是MCP
后端·程序员