入門
框架特性
- 檔案命名深受 Angular 啟發
- 完美使用 TS decorators
- 完美封裝
前置需求
- node.js version >= 10.13
- language : TS
- Nest.js version >= 8.x
安裝 Nest 與 建置腳手架
npm i -g @nestjs/cli
nest new project-name
# 運行測試
cd project-name & npm start
curl http://localhost:3000/
Hello World!%
# 可以看一下基本的專案架構
概要
Controller - 控制器
Handling Request(req) and returning Response(res) to Client
// 常用的範例:
// @nestjs/common 有很多 decorators
import { Controller, Get, Req, Res, HttpStatus } from '@nestjs/common';
import { Request, Response } from 'express';
@Controller('user') // @Controller 控制路由
export class ExamplesController {
private appService: AppService
constructor() {
this.appService = new AppService()
}
@Post('/') // 最基本的 @Get @Post @Patch @Delete RESTFul-API 設計模式
async create(@Req() req: Request, @Res() res: Response): Promise<Response> {
const result = await this.appService.create(req.body);
return res.status(HttpStatus.CREATED).set(result.header).send(result.body);
}
}
CLI
nest g controller <controller-name>
Provider - 供應商
處理邏輯的 Class 稱之 Provider 或 Service ,注入 Controller 使用
// 常用的範例:
// @nestjs/common 有很多 decorators
import { Injectable } from '@nestjs/common';
import { User } from './interfaces/user.interface';
@Injectable() // @Injectable 聲明此Class 可以被 Nest IoC container 控管
export class UsersService {
private readonly users: User[] = [];
create(user: User) { this.users.push(user); }
findAll(): User[] { return this.users; }
}
CLI
nest g service <provider-name>
Modules - 模塊
功能封裝,多數專案都有將功能封裝的習慣,會員/庫存/B2B封裝 等等 …
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
// @Module 封裝 Users
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
CLI
nest g module <module-name>
Middleware - 中間鍵
以往寫 Express 撰寫位置於 route function
內,Nest 封裝位置於 Moudle
且 Moudle 需實現 Middleware 必需 使用 NestModule
interface
// 第一種寫法
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('Request...');
next();
}
}
// 第二種寫法
import { Request, Response, NextFunction } from 'express';
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
};
使用方式
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { ExamplesModule } from './examples/examples.module';
@Module({
imports: [ExamplesModule],
})
export class AppModule implements NestModule { // 必需使用 `NestModule` interface
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('examplex');
}
}
consumer
- 排除 Route
consumer
.apply(LoggerMiddleware)
.exclude( // 排除
{ path: 'examples', method: RequestMethod.GET },
{ path: 'examples', method: RequestMethod.POST },
'examples/(.*)', // 路由可以用正則表達式
)
.forRoutes(ExamplesController);
- 多組 middleware
consumer
.apply(cors(), helmet(), logger)
.forRoutes(ExamplesController);
Exception filters - 異常排除
@Get()
async findAll() {
// async await 可以直接 throw Promise 這樣處理異常
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
// response: { "statusCode": ˋ403, "message": "Forbidden" }
}
Pipes - 管道 (略過,物件格式驗證等之方式,後端必須處理認為封裝使用必要性低)
Guards 衛兵
權限驗證器,常用在後台驗證權限之類的
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate( context: ExecutionContext ): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return validateRequest(request); // validateRequest() 自行實作
// return `true` 即可進行 process
// return `false` 拒絕訪問 Request
};
}
}
使用方式
@Controller('example')
@UseGuards(AuthGuard)
export class ExamplesController {}
進階
基於上方用法頂多身份驗證,若加上角色權限紀錄
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@Post()
@Roles('admin') // 自訂義的 Roles() decorator
async create(@Body() createExampleDto: CreateExampleDto) {
this.examplesService.create(createExampleDto);
}
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) { return true; }
const request = context.switchToHttp().getRequest();
const user = request.user;
return matchRoles(roles, user.roles); // 驗證 Role
}
}
Interceptors - 攔截器 (略過,用途來自各個 Req/Res 都會經過的攔截效果)
三方工具串接,Nest官方也封裝得很完善,若沒有就自行封裝使用 Service 也可以。
- Database
- Config
- Cache
- Schedule
- Queues
- Logging
- Cookies
- File-Upload
- Template View
Database 資料庫 - TypeORM
TypeORM 應該算是 NodeJS 最有名的 TypeScript ORM 工具,非常好用
npm i --save @nestjs/typeorm typeorm mysql2
使用方式
// ormconfig.json
// {
// "type": "mysql",
// "host": "localhost",
// "port": 3306,
// "username": "root",
// "password": "root",
// "database": "test",
// "entities": ["dist/**/*.entity{.ts,.js}"], // 前面這個寫法不支援 webpack 所以需要自動加載可用這寫法 autoLoadEntities: true,
// "synchronize": true
// }
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forRoot()],
})
export class AppModule {}
Config 設定檔 - dotenv
npm i @nestjs/config
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [ConfigModule.forRoot()],
})
export class AppModule {}
Caching 快取 - Redis
npm i cache-manager && npm i -D @types/cache-manager
import * as redisStore from 'cache-manager-redis-store';
import { CacheModule, Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
imports: [
CacheModule.register({
store: redisStore,
host: 'localhost',
port: 6379,
}),
],
controllers: [AppController],
})
export class AppModule {}
Task Scheduling 排程 - node-cron
npm i --save @nestjs/schedule && npm i -D @types/cron
// 引用
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
@Module({
imports: [ ScheduleModule.forRoot() ],
})
export class AppModule {}
// 若要使用
...
// https://github.com/kelektiv/node-cron 相關api
constructor(private schedulerRegistry: SchedulerRegistry) {} // 必須注入 Class this.schedulerRegistry 去使用
...
Queues 佇列
Bull 是使用 Redis 來建立 Data
佇列 有很多做法,此篇 Bull 為官方介紹
npm i @nestjs/bull bull && npm i -D @types/bull
import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bull';
@Module({
imports: [
BullModule.forRoot({
redis: {
host: 'localhost',
port: 6379,
},
}),
],
})
export class AppModule {}
// 使用
import { Injectable } from '@nestjs/common';
import { Queue } from 'bull';
import { InjectQueue } from '@nestjs/bull';
@Injectable()
export class AudioService {
constructor(@InjectQueue('audio') private audioQueue: Queue) {} // 註冊 佇列 https://docs.nestjs.com/techniques/queues
}
Logging 紀錄
import { LoggerService } from '@nestjs/common';
export class MyLogger implements LoggerService {
/**
* Write a 'log' level log.
*/
log(message: any, ...optionalParams: any[]) {}
/**
* Write an 'error' level log.
*/
error(message: any, ...optionalParams: any[]) {}
/**
* Write a 'warn' level log.
*/
warn(message: any, ...optionalParams: any[]) {}
/**
* Write a 'debug' level log.
*/
debug?(message: any, ...optionalParams: any[]) {}
/**
* Write a 'verbose' level log.
*/
verbose?(message: any, ...optionalParams: any[]) {}
}
const app = await NestFactory.create(AppModule, {
logger: new MyLogger(),
});
await app.listen(3000);
Cookies
npm i cookie-parser && npm i -D @types/cookie-parser
import * as cookieParser from 'cookie-parser';
// somewhere in your initialization file
app.use(cookieParser());
//使用
@Get()
findAll(@Req() request: Request) {
console.log(request.cookies);
// or "request.cookies['cookieKey']"
// or console.log(request.signedCookies);
}
File-Upload 上傳檔案 - multer
npm i -D @types/multer
// Single file
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file: Express.Multer.File) {
console.log(file);
}
// Array Files
@Post('upload')
@UseInterceptors(FilesInterceptor('files'))
uploadFile(@UploadedFiles() files: Array<Express.Multer.File>) {
console.log(files);
}
Template View 前端渲染 - hbs
npm i hbs
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(
AppModule,
);
app.useStaticAssets(join(__dirname, '..', 'public'));
app.setBaseViewsDir(join(__dirname, '..', 'views'));
app.setViewEngine('hbs');
await app.listen(3000);
}
bootstrap();
其他套件
Route Module
@Module({
imports: [
DashboardModule,
RouterModule.register([
{
path: 'admin',
module: AdminModule,
children: [
{
path: 'dashboard',
module: DashboardModule,
},
{
path: 'metrics',
module: MetricsModule,
},
],
},
]);
],
})
export class AppModule {}
結語:
依賴注入Dependency Injection(DI),算是常見的一個寫作方式,NestJS完美的整合了這個 Express 與 常用工具,算TypeScript最完整的 Web server 框架,直接使用絕對沒什麼問題,資料結構不一定要使用 Nest CLI 生成的那種,個人習慣怎樣的結構都好,我習慣的結構如下:
- < Project >
|- public - 共用靜態
|- views - 前端模板 hbs
|- src/
|- package/ - 與三方套件封裝或是廠商介接封裝
|- entitfies/ - typeorm
|- controller/ - route 控制
|- service/ - 商業邏輯層
|- middleware - 中間鍵
|- repository/ - typeorm 控制封裝
|- utils/ - 共用小邏輯
|- app.ts - 入口
|- .env - 環境變數
|- package.json - NodeJS
當然現在語言百花齊放,老話一句: 沒有最好,只有最合適團隊。