Skip to main content

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.tshttps://ithelp.ithome.com.tw/upload/images/20210313/20119338VciJrHpjiD.png

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 的內容,在 TodoControllerconstructor 注入 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/todoshttps://ithelp.ithome.com.tw/upload/images/20210314/2011933859rG0JqOSl.png

共享模組 (Shared Module)

在 Nest 的世界裡,預設情況下 Module 都是單例的,也就是說可以在各模組間共享同一個實例。事實上,每一個 Module 都算是共享模組,只要遵照設計原則來使用,每個 Module 都具有高度的重用性,這也是前面強調的「依照各模組的需求來串接」。這裡我們可以做個簡單的驗證,把 TodoServiceTodoModule 做匯出:

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 的內容,在 CopyTodoControllerconstructor 注入 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 來測試: https://ithelp.ithome.com.tw/upload/images/20210314/20119338jU1O5MQTd1.png

接著,我們再透過瀏覽器打開 http://localhost:3000/todos 來查看 Todo 是否有增加: https://ithelp.ithome.com.tw/upload/images/20210314/20119338lM66SB9pY6.png

這裡我們可以得出一個結論,像 Service 這種 Provider 會在 Module 中建立一個實例,當其他模組需要使用該實例時,就可以透過匯出的方式與其他 Module 共享。下方為簡單的概念圖: https://ithelp.ithome.com.tw/upload/images/20210320/201193380LmMwXhmGx.png

全域模組 (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 息息相關,下一篇會介紹這個機制,這裡就先懶人包一下今天的內容:

  1. Module 把相同性質的功能包裝在一起,並依照各模組的需求來串接。
  2. Module 擁有 controllersprovidersimportsexports 四個參數。
  3. 大部分的 Module 都是功能模組,其概念即為「把相同性質的功能包裝在一起」。
  4. 每個 Module 都是共享模組,其遵循著「依照各模組的需求來串接」的概念來設計。
  5. 透過共享模組的方式來與其他模組共用同一個實例。
  6. 可以透過全域模組來減少匯入次數,但不該把多數模組做提升,在設計上不是很理想。
  7. 善用常用模組的方式來統一管理多個常用模組。