返回
Featured image of post Nest.js 介紹

Nest.js 介紹

深受 Angular 的啟發 NodeJS Web 框架

入門

框架特性

  • 檔案命名深受 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

  1. 排除 Route
consumer
  .apply(LoggerMiddleware)
  .exclude( // 排除
    { path: 'examples', method: RequestMethod.GET },
    { path: 'examples', method: RequestMethod.POST },
    'examples/(.*)', // 路由可以用正則表達式
  )
  .forRoutes(ExamplesController);
  1. 多組 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

當然現在語言百花齊放,老話一句: 沒有最好,只有最合適團隊

comments powered by Disqus