圖解 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