💪

普段Rails使ってる俺がNestJS触ってみたら発狂した

2025/02/09に公開

なぜ触ろうと思ったか

Xを見ていると次のRailsはNestJSだ!
みたいな投稿をよく見かけるし注目を浴びているなーと遠目で見てた。
そろそろ触ってみるかと思い触ってみました。

NestJSとは

雑な説明をするとRailsと同じWebアプリケーションフレームワークだ。
ただしRailsみたいにUI書けずAPI?のみ扱うWebアプリケーションフレームワークである。
https://nestjs.com/

アーキテクチャ

NestJSではザックリ下記三つレイヤーに分かれている。

  • controller
  • provider
  • module

Rails使いとしては馴染みのある名前とそうでないものがある。
各レイヤーはどんな役割なのか追っていく。
https://docs.nestjs.com/first-steps#setup

controller

これはRails使いの皆さんならご存じControllerです。
HTTPリクエストを受け付けるところです。

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();
  }
}

ただRailsと比べるとUser.findとかUser.save!とか書かないで
サービスレイヤーばっかに任せちゃってそんなんじゃファイル増えちゃって大変じゃない?
増えちゃって良いんです!責務を分離するためにむしろ必要なんです!らしい、雑。
私個人としては切り出すよりコントローラー内に定義した方が分かりやすいと思ってしまった。

Controllerに関してはイメージしやすいですよね。
の前に@なんちゃらってなんだ!?インスタンス変数か!?それともメンションか!?
これはJavaScriptで提供されているデコレーターという仕組みなんです。
Rubyでいうところmoduleのmixinみたいなものです。
https://qiita.com/shiopon01/items/fd6803f792398c5219cd#クラスに組み込んで多重継承を実現するmix-in

@Controllerとクラス名の直前に書くとコントローラーとしての機能を持たせることができる。
リソース?も@Controllerに対して引数として渡すとルーティング定義できる
クラス内で関数名の直前に@Getなどを書くとHTTPメソッドとprefix配下のURLも定義もできる。
Railsで言うとControllerの中でアクションとルーティング定義できる感じっぽい。

provider

これはRailsで言うとろのアレみたいな言い方が難しいです。
@Injectableというデコレーターをクラス名の直前に書く全般を指しているらしい。
例えばサービスレイヤーがそれに該当する。
Railsで言うとControllerのアクション処理にあたると思います。

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

Rails使っていると上記定義はController内で書いてしまってもよいのでは?と思ってしまった。
後述するmoduleがシングルトンパターンで生成されエクスポートする対象を指定できる。
これによりインスタンス生成を抑え一つの状態を他のモジュールから参照できるようになるらしい。

module

前述したcontrollerやserviceを関連付けexport定義すれば他のmoduleからimport可能。
シングルトンパターンで設計されているためインスタンス一つのみ生成される。
これにより下記メリットがあるらしい。

  • インスタンス生成を抑えメモリ節約
  • 状態の不整合をなくし原因が特定しやすい
    Javascriptがシングルスレッドで実行されるのと相性が良く上記メリットを享受できる。
    曖昧なので理解を深めたい。。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

ふと思ったがどのモジュールからどのモジュールを参照しているか明確になる。
また参照したモジュールがどのモジュールを参照しているかも明確になるなと。
これはコードを整理する上では良さそうだなと思った。
どのコードへ影響を与えているか明確になり責務も明確になりそう、雑。

気になったこと

  • moduleがシングルトンパターンで生成される
    • ルートモジュールから末端アプリケーショングラフまでが深くなる可能性
    • そうなればメモリ節約できたとしてもチリツモでいつかメモリリークしないか
    • またルートモジュールのimportsプロパティの配列要素が凄くなりそうな未来しか見えない
  • モデルが出てこない
    • 生成される実装で言うテキスト返すならそもそも必要ないな
    • しかしDB操作を行いレスポンス返すならモデルはどうやって書くのか
  • ORMも標準で定義されていない
    • アクティブレコードに依存などがない
    • 良しなに選定でき柔軟性がありそう
  • Railsと比べ不足している箇所が多い
    • つまり考える箇所が多い、ORMやモデルをどう書くかなど考える
    • それは開発生産性を向上させるアプローチなんだろうか
  • Railsと比べ新しい概念が多い
    • 私が未熟者のせいもあるが馴染みない概念多い
    • 依存性の注入、シングルトンパターン、またメリットについてなど

まとめ

Railsと比較し理解を深める中で新しい概念が出てきて勉強になり個人的に楽しかった。
ただイマイチメリットが掴めませんでした。
参考記事を見る限りかなり細かくクラス定義することで責務は分割できそうだなとは思った。
上記が後の規模が大きくなってきた保守性や拡張性を担保しやすいメリットなんだろうか、雑。
最近のホットな話題としては生成AIエディタとも相性良さそうだと思った、それは下記。

  • 型定義できる
  • コード書くべき箇所が明確にできる

次はクリーンアーキテクチャをざっくりと理解し柔軟性高そうなNestJSくんに適用したい。

最後に。。
ぎゃああああぁぁぁああぁ!

参考

https://docs.nestjs.com/
https://zenn.dev/morinokami/articles/nestjs-overview

Discussion