# nestjs 介紹與實作 day 3 - 介紹 nestjs Module 元件
## 目標
1. 介紹 nestjs 核心元件 Module
2. 說明 nestjs 建構與分享元件的設計概念
## nestjs 建構原理
### 說明
nestjs 與一般 nodejs 程式概念一樣都是把邏輯透過 module 來把邏輯做模組化
在程式啟動點,把這些模組透過工廠模式建構載入設定
當所有元建構都被初始化之後,才正式啟動服務
### 範例
使用 nestjs cli 建構一個 mountain_climb 專案
```shell=
nest new mountain_climb
```
長出來的專案結構如下:

然後找尋 package.json 察看 entry point 可能在 script 的點
發現

從 start:prod 看起來
有可能是 main.ts 這個檔案
但是實際上是怎麼鏈結 需要看一下 nest-cli.json 這個檔案
打開一看

發現 , holy 媽祖!根本沒特別指定
這時候就可以查看官網 關於 [nestjs cli 設定章節](https://docs.nestjs.com/cli/monorepo#workspace-projects)
會發現有寫一行很隱諱的寫著預設 application 需要一個 main.ts
所以預設的 entry point 就是 main.ts
雖然說打開 main.ts 也可以發現內容有些關於建置跟啟動的部份
但是如果我不想要 entry point 叫作這個名字呢
那就需要自己在 nest-cli.json 加入一個 entryFile 的設定
舉例如下:


然後執行
```shell=
pnpm start:dev
```

就會發現可以正常運行了
但這邊為了符合常規設定,所以我們還是回復預設值 main.ts 或直接移除 entryFile 這欄
**Note**: 改回來的同時 entry point 的檔案也需要改回來
### main 建構解析
接著可以來看 main.ts 內容
```typescript=
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
```
分為兩個部份
1. 建構
2. 執行
#### 執行
雖然順序是先建構再執行
這邊因為執行比較短,所以先講
真正再呼叫程式的只有第 8 行, bootstrap()
其他部份都是在做建構
#### 建構
第1部份就是使用 NestFactory 這個建構 Factory 來做所有元件的初始化
包含在每個被標注成 @Module 的元件都會被 Factory 依據 constructor 的設定來建構
特別注意的是 nestjs 使用 di container
所有元件的建構順序會依照注入設定依序注入
最後根據 app 特性去啟動
比如這邊是使用 web server 所以會使用 app.listen
如果是其他服務 也可以使用 app.start 的方式來啟動
## nestjs 元件介紹
### 最基礎單位是 Module

### 透過 Provider 來使用共用服務或是元件
app.module.ts:
```typescript=
import { Module } from '@nestjs/common';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [],
providers: [AppService],
exports: []
})
export class AppModule {}
```
透過 Injectable 關鍵字讓 Service 可以被 provide
app.service.ts
```typescript=
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
```
### controllers 用來放置與 HTTP 互動的邏輯
app.module.ts:
```typescript=
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
```
app.controller.ts:
```typescript=
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
```
### imports 用來引入外部非全域的 Module
## nestjs 如何分享元件
在 nestjs 雖然與 Angular 框架類似有 Module 元件以及 DI Container 概念
然而不同的是, Angular 一旦註冊 Module 就是全局元件
但 nestjs 並非如此,需要特別設定可以註冊是否需要全局元件,預設不是全局
而需要透過 import 的方式來引入
### 全局引入
代表該 Module 只需要在最外層的 root module 內 import
即可在其他 Module 內使該元件 export 出來的功能
而該 Module 在宣告時,需要再上面加入 @Global 這個修飾子
舉例:
user.module.ts
```typescript=
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
@Global()
@Module({
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
```
book.module.ts
```typescript=
import { Module } from '@nestjs/common';
import { BookController } from './book.controller';
import { BookStoreService } from './book-store.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BookStoreEntity } from './book-store.entity';
@Module({
imports: [TypeOrmModule.forFeature([BookStoreEntity])],
controllers: [BookController],
providers: [BookStoreService],
})
export class BookStoreModule {}
```
book.controller.ts
```typescript=
import {
Controller,
Get
Query,
} from '@nestjs/common';
import { BookStoreDto } from './dtos/book-store.dto';
import { BookStoreService } from './book-store.service';
import { UserService } from './user/user.service';
@Controller('books')
export class BookController {
private logger = new Logger(BookController.name);
constructor(
private readonly bookStoreService: BookStoreService,
private readonly userService: UserService,
) {}
@Get('/user')
async sayHi(@Query('user') user: string) {
this.logger.log({ user });
return user + ' says ' + this.userService.greeting();
}
}
```
比如 logger module 或是一些 global 連線比如 db connection 或是 redis 等等
### 逐個在需要的地方引入
代表該 Module 屬於非全局 module ,只有在引入的 Module 才能使用內部 export 出來的服務
舉例:
user.module.ts
```typescript=
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
@Module({
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
```
book.module.ts
```typescript=
import { Module } from '@nestjs/common';
import { BookController } from './book.controller';
import { BookStoreService } from './book-store.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BookStoreEntity } from './book-store.entity';
import { UserModule } from './user/user.module';
@Module({
imports: [TypeOrmModule.forFeature([BookStoreEntity]), UserModule],
controllers: [BookController],
providers: [BookStoreService],
})
export class BookStoreModule {}
```
book.controller.ts
```typescript=
import {
Controller,
Get
Query,
} from '@nestjs/common';
import { BookStoreDto } from './dtos/book-store.dto';
import { BookStoreService } from './book-store.service';
import { UserService } from './user/user.service';
@Controller('books')
export class BookController {
private logger = new Logger(BookController.name);
constructor(
private readonly bookStoreService: BookStoreService,
private readonly userService: UserService,
) {}
@Get('/user')
async sayHi(@Query('user') user: string) {
this.logger.log({ user });
return user + ' says ' + this.userService.greeting();
}
}
```
## 理想上的分享方式
一般來說,全局引用可以很方便的去複用共同使用的功能
然而代價就是會有全局汙染,因為所有 module 都認得該 module 的 Instance
最理想的方式是可以把,引用限制該功能模組之內
這樣再拔除或是修改該模組時,影響的範圍就會比較小
### 非公用模組
所以在 layout 上
同常會把同模組相關的東西放在同一個 folder
如下

在 auth app 下,除了 root module 之外還有一個 users module
users module 的相關邏輯會放在一個 users 資料夾
這樣在抽換 users module 或是 debug 時也會比較好限縮範圍找 bug
### 公用模組
公用的 module 會統一放個叫作 lib 的 folder 下

這是一般比較偏近官方的作法
如果要自己去額外變 layout 最好有自己一套的設計原則
否則真的就是降低可維護性,提高閱讀成本
### 暗黑兵法
自己創一套模改版的 layout 只有自己看的懂
改名叫作 clean architecture
這樣就可以達到只有自己看的懂,別人改不動的境地了
別人問就說為了符合 clean architecture 或是什麼 N 角架構
建構了一個護城河 silo