nestjsを触ってみた
https://nestjs.com/
nestjsというnodeのサーバーサイドフレームワークを調査する機会があったので軽く触った感想をまとめました。
今回触ったものはこちらのリポジトリにまとめています。
対象読者
- nestjsって何?って人
- nestjsでざっくりどんなことができるのか知りたい人
公式ドキュメントの読み合わせくらいのレベルの記事です。
なので感想だけ見れればいいやって人は一番下まで飛ばしていただけると・・・!
nestjsとは?
A progressive Node.js framework for building efficient, reliable and scalable server-side applications.
「サーバーサイドのプログレッシブnodeフレームワーク」とのこと。
そのほか特徴としては
- typescriptを完全にサポートしている
- expressをラップしている
- expressやfastfyより高いレイヤーの抽象化をしている。
- nodeのrailsみたいなイメージ?
- angularに強く影響を受けているらしい(確かに)
公式のドキュメントに沿って触ってみた
setup
脳死でできます。
npm i -g @nestjs/cli
nest new project-name
src配下に以下のようなファイルができていました。
src
┣ app.controller.ts
┣ app.service.ts
┣ app.module.ts
┗ main.ts
ここからはoverviewの項目に沿って、機能を見ていきたいと思います。
controller
CLIで生成できます。
nest g controller cats
import { Controller, Get, HttpCode, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
@Get('not-implemented')
@HttpCode(501)
handleNotImplemented(): string {
return 'Not implemented'
}
@Get('express')
handleExpress(@Req() request: Request): string {
console.log(request)
return 'Use express request object sample.'
}
}
- controllerのクラスに付与するデコレータと関数のデコレータの組み合わせでルーティングが決定される
- 大概のものはデコレータで設定できる
- 推奨はしないが、expressのリクエストオブジェクトにもアクセスできる
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsController } from './cats/cats.controller'; // <= 自動で追加してくれてた
@Module({
imports: [],
controllers: [AppController, CatsController], // <= 自動で追加してくれてた
providers: [AppService],
})
export class AppModule {}
最後に、上記のようにmoduleにコントローラーを追加します。って試そうと思ったらここもCLIでやってくれていた。
参考: https://docs.nestjs.com/controllers
provider
nestjsでいうproviderとは何か?
Providers are a fundamental concept in Nest. Many of the basic Nest classes may be treated as a provider – services, repositories, factories, helpers, and so on. The main idea of a provider is that it can inject dependencies; this means objects can create various relationships with each other, and the function of "wiring up" instances of objects can largely be delegated to the Nest runtime system. A provider is simply a class annotated with an @Injectable() decorator.
ざっくり、様々な依存関係を @Injectable
デコレータを使って関連づけることで、nest runtimeがよしなにやってくれる。ここをちゃんとやるとSOLID原則に従うことができるよって機能かと思います。
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}
公式そのままだが、こんな感じのイメージ。
import { Controller, Get, HttpCode, Req, Post, Body } from '@nestjs/common';
import { Request } from 'express';
import { CatsService } from './cats.service'
import { Cat } from './interfaces/cat.interface'
import { CreateCatDto } from './dto/create-cat.dto'
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll()
}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto)
}
@Get('not-implemented')
@HttpCode(501)
handleNotImplemented(): string {
return 'Not implemented'
}
@Get('express')
handleExpress(@Req() request: Request): string {
console.log(request)
return 'Use express request object sample.'
}
}
このようにconstructorを通してinjectされる。
- nestjsはDIを強く推している
- リポジトリだったり、ヘルパーだったりもこのようにinjectするのが良いとしている
module
A module is a class annotated with a @Module() decorator. The @Module() decorator provides metadata that Nest makes use of to organize the application structure.
Each application has at least one module, a root module. The root module is the starting point Nest uses to build the application graph - the internal data structure Nest uses to resolve module and provider relationships and dependencies. While very small applications may theoretically have just the root module, this is not the typical case. We want to emphasize that modules are strongly recommended as an effective way to organize your components. Thus, for most applications, the resulting architecture will employ multiple modules, each encapsulating a closely related set of capabilities.
nestjsがアプリケーション構造を整理するためのメタデータ。アプリケーションにはルートモジュールと、それにぶら下がるモジュールがある。とのこと。
ドメインだったり、CRUDのモデルごとにモジュールを作り、それに紐づく機能をモジュールに入れていくイメージかな?
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService]
})
export class CatsModule {}
このように上記で作ったcontrollerやserviceを入れたmoduleを作り
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
controllers: [AppController, CatsController],
providers: [AppService, CatsService],
})
export class AppModule {}
これが
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
こうなる。良さそう。
- 構造を整理するのに役立つ
- デフォルトでシングルトンになっているので、モジュール間でserviceの共有もできるらしい(今回は割愛)
middleware
Middleware is a function which is called before the route handler. Middleware functions have access to the request and response objects, and the next() middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.
ルートハンドラの前に呼び出される関数だよ。よくあるやつ。
NestMiddlewareInterfaceを実装する必要がある。
import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response } from 'express'
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
console.log('Request...')
next()
}
}
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';
import { LoggerMiddleware } from './logger.middleware'
@Module({
imports: [CatsModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('cats')
}
}
このようにmiddlewaremもDIできる。
- consumerの
apply
とforRoutes
で細かな制御も可能
exception filters
Nest comes with a built-in exceptions layer which is responsible for processing all unhandled exceptions across an application. When an exception is not handled by your application code, it is caught by this layer, which then automatically sends an appropriate user-friendly response.
nestjsは組み込みの例外フィルターがある。例外に関する処理がない場合は、ここが自動的にユーザーフレンドリーな例外を返してくれるよ。
@Get('cats-exception')
async findAllError() {
throw new HttpException({
status: HttpStatus.FORBIDDEN,
error: 'This is a custom message'
}, HttpStatus.FORBIDDEN)
}
HttpEcceptionクラスにresponse(string or object)とステータスコードを設定する。
ここも機能が多そうだったのでその他は割愛します。
- 基本の例外時のレスポンスをカスタマイズすることも可能
-
HttpException
を継承する標準の例外用関数も提供されている - メソッド、コントローラー、グローバル様々なスコープで例外の挙動を設定できる
pipe
A pipe is a class annotated with the @Injectable() decorator. Pipes should implement the PipeTransform interface.
exception filterの後に実行されるような関数のこと。主な使用例としては
- リクエストを整形する(string => integer的な)
- validation(有効なデータじゃなかった場合に設定しているhandlerではなく例外をスローする)
コントローラーの関数の前に実行される。
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id)
}
ここも機能が多そうなので割愛します。
- クエリもパイプでいじることができる
- パイプでハンドリングするエラーも設定できる
- カスタムパイプも作成できる
- クラス、オブジェクトのスキーマに対してもバリデーションをかけれる
-
exception filter
同様様々なスコープに設定できる
guard
Guards have a single responsibility. They determine whether a given request will be handled by the route handler or not, depending on certain conditions (like permissions, roles, ACLs, etc.) present at run-time. This is often referred to as authorization. Authorization (and its cousin, authentication, with which it usually collaborates) has typically been handled by middleware in traditional Express applications. Middleware is a fine choice for authentication, since things like token validation and attaching properties to the request object are not strongly connected with a particular route context (and its metadata).
But middleware, by its nature, is dumb. It doesn't know which handler will be executed after calling the next() function. On the other hand, Guards have access to the ExecutionContext instance, and thus know exactly what's going to be executed next. They're designed, much like exception filters, pipes, and interceptors, to let you interpose processing logic at exactly the right point in the request/response cycle, and to do so declaratively. This helps keep your code DRY and declarative.
ミドルウェアに似ているものだが次の特徴がある。
- 権限、ロール、ACLの制御をするのに特化しているイメージ
- 実行順序でいうとミドルウェアの後、パイプやインターセプターの前に実行される
- 次に何が実行されているかを知っている
- 特定のメタデータに基づいた処理に特化している
- サンプルで記載されていたのはrole
interceptor
Interceptors have a set of useful capabilities which are inspired by the Aspect Oriented Programming (AOP) technique. They make it possible to:
- bind extra logic before / after method execution
- transform the result returned from a function
- transform the exception thrown from a function
- extend the basic function behavior
- completely override a function depending on specific conditions (e.g., for caching purposes)
用途の箇所を直訳すると
- メソッド実行の前/後に追加のロジックをバインドする
- 関数から返された結果を変換する
- 関数からスローされた例外を変換する
- 基本的な関数の動作を拡張する
- 特定の条件に応じて関数を完全にオーバーライドする(たとえば、キャッシュの目的で)
と記述されている。が、正直middlewareの扱いもここまで細かく分けてくれているのであんまり使わなさそう・・・と思いここも割愛します。
ざっくりサンプルを読むと、ハンドラーの関数の前後を細かく制御できるそう。
その他
多機能すぎて読みきれません
感想
- サーバーサイドのフレームワークとしては大体機能揃ってそう
- フレームワークの指針に従えば保守性の高いコードを書けそう
- 詳細は見ていないがMVCで言うモデルの機能も持っていて、MVCでサーバーサイド全部まかなえそう
- typesafeに書けるの良い。イメージ的には比較対象はrailsとかかなって思った
- 最近流行りのnextjsとかnuxtjsのカスタムサーバーとして使うにはちょっと機能が多すぎるし使わなさそうな気がした
- CLIが充実していて良い
ドキュメントにはおそらく書いてない?と思いますが、CLIでプロジェクトを生成した際に
- prettier
- eslint
- test(jest)
の設定も完了している状態で生成してくれているのはとても良いなと思いました。
それと別でドキュメントがよくできていると思いました。
フレームワークとしての機能としての説明と共に設計の指針も示してくれていて、チュートリアルをやるだけで本読んだだけで勉強した気になるより為になるなって思いました。