圖解 Nestjs - 適合中國寶寶的入門指導
筆者入門 Nest 的時候屬實是迷糊了一陣,本文將從初學者的視角出發,試圖爲大家解釋 Nestjs 到底是如何運作的。如有錯誤歡迎指出,謝謝~
假設我們來做這樣一個服務:寶可夢大全
提供四個接口:
-
獲取完整的寶可夢列表
-
根據寶可夢編號獲取某一隻寶可夢的信息
-
獲取完整的技能列表
-
根據某個技能獲取可以學會該技能的寶可夢列表
Module = 模塊
Module,中文譯作模塊,我們將從它來入手,搞清楚 Nestjs 大概是如何工作的。
官網這張圖表達的很清楚,Nest 的大致理念就是一顆 “模塊樹”,從根模塊出發,連接到許多的子功能模塊。
我們的寶可夢查詢的結構可能是這樣的:
(本文中我們使用 Prisma 來操作數據庫)
現在讓我們聚焦到其中一個功能模塊:Pokemon Module
細說 Pokemon Module
這個模塊的領域是寶可夢,顯然這是一個非常核心的模塊。那麼 “模塊”,又是怎麼運作的呢?
從外部看,“模塊” 是一個黑盒,有自己的輸入和輸出:
而黑盒的內部,則主要是兩部分:
-
Services
-
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,取決於從什麼視角去看他。因此,在代碼中它往往表現爲這樣:
// nestjs project directory
|-pokemon
| |_pokemon.module.ts
| |_pokemon.controller.ts
| |_pokemon.service.ts
|-......
// pokemon.module.ts
@Module({
controllers: [PokemonController],
providers: [PokemonService],
imports: [PrismaModule],
exports: [PokemonService],
})
// 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);
// ......
}
}
// 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中,我們的代碼依然可以運行:
// pokemon.module.ts
@Module({
controllers: [PokemonController],
providers: [PokemonService, PrismaService],
imports: [],
exports: [PokemonService],
})
直接先說結論:用 Import。
具體來說,只要是用外部服務(也就是除去 PokemonModule 將 PokemonService 作爲 Provider 這種情況),使用 import 更爲合適。
區別在於:import module 會複用已經創建的實例,而每個 provider 都會創建新的實例。
前者佔用更少的內存,且一個可複用的實例在多處被使用在需求中也更加常見。
// 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 裏
比如這樣:
// pokemon.module.ts
@Module({
controllers: [PokemonController],
providers: [PokemonService],
imports: [PrismaModule],
exports: [PokemonService],
})
// skill.module.ts
@Module({
controllers: [SkillController],
providers: [SkillService],
imports: [PrismaModule, PokemonModule],
})
好了,恭喜你,你已經完全掌握 Nestjs 了!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/PT7QvvKCSu75R3Z1wYtl7A