module
建置 Module
所有的 Module 都必須使用 @Module
裝飾器來定義。可以用 NestCLI 快速生成 Module:
$ nest generate module <MODULE_NAME>
注意:
<MODULE_NAME>
可以含有路徑,如:features/todo
,這樣就會在src
資料夾下建立該路徑並含有 Module。
這邊我建立了一個名為 todo
的 Module:
$ nest generate module features/todo
提醒:如果先前有按照教學建立
TodoController
的話,可以先移除,這邊將會建立新的 Controller。
在 src/features
底下會看見一個名為 todo
的資料夾,裡面有 todo.module.ts
:
todo.module.ts
的內容如下:
import { Module } from '@nestjs/common';
@Module({})
export class TodoModule {}
參數介紹
在建立完 Module 後會發現 @Module
裝飾器裡面只有一個空物件,這是因為 NestCLI 不確定使用者建立該模組的用途為何,所以留空給使用者自行填入。那具體有哪些參數可以使用呢?共有以下四大項目:
controllers
:將要歸納在該 Module 下的 Controller 放在這裡,會在載入該 Module 時實例化它們。providers
:將會使用到的 Provider 放在這裡,比如說:Service。會在載入該 Module 時實例化它們。exports
:在這個 Module 下的部分 Provider 可能會在其他 Module 中使用,此時就可以把這些 Provider 放在這裡進行匯出。imports
:將其他模組的 Provider 匯入。
提醒:Provider 會在後面篇章做更詳細的說明。
功能模組 (Feature Module)
大多數的 Module 都屬於功能模組,其概念就是前面一直強調的:把相同性質的功能包裝在一起。這邊我們就先把 Controller 加到 Module 中,透過指令建立 Controller:
$ nest generate controller <CONTROLLER_NAME>
這邊我指定的 <CONTROLLER_NAME>
為 features/todo
,會看到 TodoModule
自動匯入了該 Controller 到 controllers
裡:
import { Module } from '@nestjs/common';
import { TodoController } from './todo.controller';
@Module({
controllers: [TodoController]
})
export class TodoModule {}
前面有提過一個含有路由功能的模組通常都有 Controller 與 Service,這邊我們先透過指令產生一個 Service,後續會再針對 Service 做說明:
$ nest generate service <SERVICE_NAME>
這邊我指定的 <SERVICE_NAME>
為 features/todo
,會看到 TodoModule
自動匯入了該 Service 到 providers
裡:
import { Module } from '@nestjs/common';
import { TodoController } from './todo.controller';
import { TodoService } from './todo.service';
@Module({
controllers: [TodoController],
providers: [TodoService]
})
export class TodoModule {}
稍微修改一下 todo.service.ts
的內容,大致上就是在 TodoService
建立一個 getTodos
方法回傳 todos
的資訊:
import { Injectable } from '@nestjs/common';
@Injectable()
export class TodoService {
private todos: { id: number, title: string, description: string }[] = [
{
id: 1,
title: 'Title 1',
description: ''
}
];
getTodos(): { id: number, title: string, description: string }[] {
return this.todos;
}
}
然後再修改 todo.controller.ts
的內容,在 TodoController
的 constructor
注入 TodoService
:
import { Controller, Get } from '@nestjs/common';
import { TodoService } from './todo.service';
@Controller('todos')
export class TodoController {
constructor(
private readonly todoService: TodoService
) {}
@Get()
getAll() {
return this.todoService.getTodos();
}
}
這樣就完成一個可以作動的功能模組了,那要如何使用它呢?很簡單,只要在根模組匯入它就可以了,不過在產生 Module 的時候就自動匯入了,不需要手動去新增,是不是很方便呢!趕快打開瀏覽器查看 http://localhost:3000/todos :
共享模組 (Shared Module)
在 Nest 的世界裡,預設情況下 Module 都是單例的,也就是說可以在各模組間共享同一個實例。事實上,每一個 Module 都算是共享模組,只要遵照設計原則來使用,每個 Module 都具有高度的重用性,這也是前面強調的「依照各模組的需求來串接」。這裡我們可以做個簡單的驗證,把 TodoService
從 TodoModule
做匯出:
import { Module } from '@nestjs/common';
import { TodoController } from './todo.controller';
import { TodoService } from './todo.service';
@Module({
controllers: [TodoController],
providers: [TodoService],
exports: [TodoService]
})
export class TodoModule {}
接著,建立一個新的 Module 與 Controller,這裡我使用的指令如下:
$ nest generate module features/copy-todo
$ nest generate controller features/copy-todo
這邊調整一下 todo.service.ts
的內容,在 TodoService
新增一個 createTodo
的方法:
import { Injectable } from '@nestjs/common';
@Injectable()
export class TodoService {
private todos: { id: number, title: string, description: string }[] = [
{
id: 1,
title: 'Title 1',
description: ''
}
];
getTodos(): { id: number, title: string, description: string }[] {
return this.todos;
}
createTodo(item: { id: number, title: string, description: string }) {
this.todos.push(item);
}
}
在 CopyTodoModule
裡匯入 TodoModule
:
import { Module } from '@nestjs/common';
import { TodoModule } from '../todo/todo.module';
import { CopyTodoController } from './copy-todo.controller';
@Module({
controllers: [CopyTodoController],
imports: [TodoModule]
})
export class CopyTodoModule {}
修改 copy-todo.controller.ts
的內容,在 CopyTodoController
的 constructor
注入 TodoService
,並建立一個方法來調用 createTodo
:
import { Body, Controller, Post } from '@nestjs/common';
import { TodoService } from '../todo/todo.service';
@Controller('copy-todos')
export class CopyTodoController {
constructor(
private readonly todoService: TodoService
) {}
@Post()
create(@Body() body: { id: number, title: string, description: string }) {
this.todoService.createTodo(body);
return body;
}
}
透過 Postman 來測試:
接著,我們再透過瀏覽器打開 http://localhost:3000/todos 來查看 Todo 是否有增加:
這裡我們可以得出一個結論,像 Service 這種 Provider 會在 Module 中建立一個實例,當其他模組需要使用該實例時,就可以透過匯出的方式與其他 Module 共享。下方為簡單的概念圖:
全域模組 (Global Module)
當有 Module 要與多數 Module 共用時,會一直在各 Module 進行匯入的動作,這時候可以透過提升 Module 為 全域模組,讓其他模組不需要匯入也能夠使用,只需要在 Module 上再添加一個 @Global
的裝飾器即可。以 TodoModule
為例:
import { Module, Global } from '@nestjs/common';
import { TodoController } from './todo.controller';
import { TodoService } from './todo.service';
@Global()
@Module({
controllers: [TodoController],
providers: [TodoService],
exports: [TodoService]
})
export class TodoModule {}
注意:雖然可以透過提升為全域來減少匯入的次數,但非必要情況應少用,這樣才是好的設計準則。
常用模組 (Common Module)
這是一種設計技巧,Module 可以不含任何 Controller 與 Provider,只單純把匯入的 Module 再匯出,這樣的好處是可以把多個常用的 Module 集中在一起,其他 Module 要使用的話只需要匯入此 Module 就可以了。下方為範例程式碼:
@Module({
imports: [
AModule,
BModule
],
exports: [
AModule,
BModule
],
})
export class CommonModule {}
小結
Module 在 Nest 是非常重要的角色,特別是有很核心的機制與 Provider 息息相關,下一篇會介紹這個機制,這裡就先懶人包一下今天的內容:
- Module 把相同性質的功能包裝在一起,並依照各模組的需求來串接。
- Module 擁有
controllers
、providers
、imports
與exports
四個參數。 - 大部分的 Module 都是功能模組,其概念即為「把相同性質的功能包裝在一起」。
- 每個 Module 都是共享模組,其遵循著「依照各模組的需求來串接」的概念來設計。
- 透過共享模組的方式來與其他模組共用同一個實例。
- 可以透過全域模組來減少匯入次數,但不該把多數模組做提升,在設計上不是很理想。
- 善用常用模組的方式來統一管理多個常用模組。