[推薦] 優雅易維護的 Node JS API 框架 – NestJS

在開發 API 的時候, 往往會需要選擇一個合適的 API 框架, 在 NodeJS 常常看到的選擇可能會是輕量級框架 ExpressJS, 不過因為 ExpressJS 較輕量, 對於一些繁複的商業邏輯價就需要自己處理, 而這推薦一個精美且優雅 (隔離的、可測試的) API 框架 NestJS, 其直接內建 hot reload server 以及使用 typescript, 且文件非常完整, 在開發上做到開箱即用易上手的好處。

基本資訊

官方網站:https://nestjs.com/
原始碼:https://github.com/nestjs/nest
文件 (英文):https://docs.nestjs.com/
文件 (簡體中文):https://docs.nestjs.cn/

在 Github 星星數快速的成長, 下圖為 Express js 與 NestJS 星星數成長的比較

介紹

NestJS 是一個強大且功能完善的 API 框架, 除了擁有許多 API 常用的的功能以外, 還吸收了許多其他框架不錯的特性, 例如 DI (Dependency Injection), Service Provider, AOP (Aspect Oriented Programming), Decorator 表達式 (類似 Java Spring 的 annotation) … 等, 這些特性在不同程式語言中常常都會看到, 如果你剛好學過 Java 的 Spring Boot 那你可能在寫部分功能時會感覺自己是正在使用 Spring Boot, 這些特性非常適合中大型專案,可以增加程式的易維護性以及擴充性

以下為 NestJS 的優點:

  • 功能性完整, 整合大部分後端需要使用到的功能
  • 完整模組化, 有需要才進行安裝載入並可依照情境置換不同的實作, 專案可以輕量也可以組成大型架構
  • 完善的使用 Typescript Decorator 使程式碼更簡潔易讀
  • 文件完整, 且支援多國語言, 對新手容易上手
  • 使用 Typescript 增加可維護性
  • 支援 IoC Container (Dependency Injection), AOP (Aspect-Oriented Programming)… 等不錯的框架特性
  • 使用方式直覺, 擁有不錯的開發體驗

安裝與使用

要建立專案需要先安裝 Nest CLI, 輸入指令安裝:

npm i -g @nestjs/cli

當安裝完成後接著輸入指令建立專案:

nest new xenby-project

建立出來的專案檔案數量不多, 只有 controller 與 service 一些範例程式碼與設定檔

這時在專案根目錄輸入指令啟動伺服器就可以開始開發了

npm run start:dev

伺服器會啟動在 localhost:3000 中

基本功能介紹

因 NestJS 功能非常多, 無法全部講解, 這邊介紹基本的 API 三層式架構 (API, Service, Repository) 用法, 更深入的部分可以翻閱官方文件教學

Controller

寫 API 基本上少不了路由與 API Controller, 要讓 Nest 認定該 Class 是 Controller 方案是非常簡單, 只需要在 Class 上標記 @Controller() 並且在 method 上面標記 @Get, @POST… 等就能完成路由設定

// xenby.controller.ts
import { Controller, Get } from '@nestjs/common';

@Controller()
export class XenbyController {
  @Get('/hi-xenby')
  getHi(): string {
    return 'hi, xenby';
  }
}

並且透過 app.module.ts 導入該 Controller 就能運作了

// app.module.ts
import { Module } from '@nestjs/common';
import { XenbyController } from './xenby.controller';

@Module({
  imports: [],
  controllers: [XenbyController],
  providers: [],
})
export class AppModule {}

Service

三層式架構中負責處理商業邏輯的部分為 Service 層, 而在中型以上的框架都會支援 Dependency Injection 方式 (例如:PHP 的 Laravel, Java 的 Spring Framework… 等), 達到控制反轉的目的, 在 Controller 不使用 New 的方式來建立 Service 而是透過注入的方式來使用, 使得 Controller 層使用時不需要了解 Service 內的相依性

要讓 Class 能夠注入其他模組只需要在 Service 上加上 @Injectable() 標記, 並且在 Module 上註冊此 Service 為 Provider

// xenby.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class XenbyService {
  getBlogUrl(): string {
    return 'https://xenby.com';
  }
}
// app.module.ts
import { Module } from '@nestjs/common';
import { XenbyController } from './xenby.controller';
import { XenbyService } from './xenby.service';

@Module({
  imports: [],
  controllers: [XenbyController],
  providers: [XenbyService],
})
export class AppModule {}

這樣就可以直接在 Controller 中注入使用了

// xenby.controller.ts
import { Controller, Get } from '@nestjs/common';
import { XenbyService } from './xenby.service';

@Controller()
export class XenbyController {
  constructor(private readonly xenbyService: XenbyService) {}

  @Get('/xenby-url')
  getUrl(): string {
    return this.xenbyService.getBlogUrl();
  }
}

Repository

NestJS 中並沒有自己實作 ORM 與 Repository, 而是整合進 typeorm 及 sequelize, 可以依據自己的使用習慣選擇想要的 ORM

這邊使用 typeorm 與 mysql 做範例, 首先要安裝 typeorm 與 mysql 套件:

npm install --save @nestjs/typeorm typeorm mysql2

並且在根目錄建立 ormconfig.json, 在裡面設定資料庫連線資訊

其中 entities 設定為 "dist/**/*.entity{.ts,.js}" 為讓 typeorm 能夠自動載入 entities

{
  "type": "mysql",
  "host": "localhost",
  "port": 3306,
  "username": "xenby",
  "password": "password",
  "database": "xenby-blog",
  "entities": ["dist/**/*.entity{.ts,.js}"],
  "synchronize": true
}

接著只要在 app.module.ts 引入 TypeOrmModule 就會自動配置連線

// app.module.ts
import { Module } from '@nestjs/common';
import { XenbyController } from './xenby.controller';
import { XenbyService } from './xenby.service';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [TypeOrmModule.forRoot()],
  controllers: [XenbyController],
  providers: [XenbyService],
})
export class AppModule {}

這時候就可以開始用建立 Entity 與 Repository

※ 詳細 typeorm 用法可以參考官方文件:https://typeorm.io/

// post.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  content: string;

  @Column({ default: true })
  isActive: boolean;
}
// post.repository.ts
import { EntityRepository, Repository } from 'typeorm';
import { Post } from './post.entity';

@EntityRepository(Post)
export class PostRepository extends Repository<Post> {
  async findById(id: number): Promise<Post> {
    return await this.findOne({ id });
  }
}

並且在 Module 中將 Repository 註冊為 Provider

// app.module.ts
import { Module } from '@nestjs/common';
import { XenbyController } from './xenby.controller';
import { XenbyService } from './xenby.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostRepository } from './post.repository';

@Module({
  imports: [TypeOrmModule.forRoot()],
  controllers: [XenbyController],
  providers: [XenbyService, PostRepository],
})
export class AppModule {}

這樣就能夠在 Service 層注入 Repository 使用了

// xenby.service.ts
import { Injectable } from '@nestjs/common';
import { PostRepository } from './post.repository';

@Injectable()
export class XenbyService {
  constructor(private postRepository: PostRepository) {}

  async getPostContent(id: number): Promise<string> {
    const post = await this.postRepository.findById(id);
    return post.content;
  }
}

結語

這邊介紹只有 Demo NestJS 中冰山一角的功能, 其他功能還包括 Middleware, Validation, Pipes, Guards, Exception filters… 等許多的功能, 如果對於其他功能有興趣可以去翻閱一下官方文件,相信對於專案開發會有很大的幫助。

發表迴響