NestJS

NestJS と TypeORM の基本設定・ベストプラクティス徹底解説

ⓘ本ページはプロモーションが含まれています

Contents

スポンサードリンク

1. 環境別 DataSource 設定と DI プロバイダー化

1-1 概要

NestJS アプリは開発・ステージング・本番の3つ以上の環境で動かすことが前提です。データベース接続情報を .env 系ファイル に分離し、@nestjs/configTypeOrmModule.forRootAsync() を組み合わせることで、起動時に自動的に正しい設定がロードされます。本章では DataSource のプロバイダー化 までを網羅します。

1-2 環境ファイルの配置例

ファイル 主な用途
.env.development ローカル開発用(DB はローカル PostgreSQL など)
.env.staging ステージング環境(CI/CD のプレビューサーバー)
.env.production 本番環境(高可用性 DB クラスタ)

1-3 ConfigModule の具体的設定

DataSource プロバイダーの実装

  • ポイント
  • provide に文字列トークン 'DATA_SOURCE' を使用すれば、任意のサービスで @Inject('DATA_SOURCE') と注入可能。
  • useFactory 内で initialize() を呼び出すことで、モジュールロード順に依存せずに接続が確立します。

1-4 まとめ(Key Takeaways)

  • .env.* を分割し、ConfigModule.forRoot({ envFilePath: [...] }) で自動切替。
  • TypeOrmModule.forRootAsync() と DI により環境変数を安全に注入。
  • DataSourceProvider で DataSource をトークン化すれば、サービス層・リポジトリ層のどこからでも取得可能。

2. エンティティ設計のベストプラクティス

2-1 概要

エンティティはデータベースとビジネスロジックを結びつける重要な橋です。命名規則・カラム定義・インデックス設計を統一しないと、マイグレーションの差分が増えて保守コストが急上昇します。本章では 可読性・パフォーマンス・整合性 の三本柱に沿った実装例を示します。

2-2 命名規則とカラムオプション

要素 推奨スタイル
エンティティクラス 単数形 PascalCaseUser, OrderItem
テーブル名 スネークケース+複数形users, order_items
カラム名 スネークケース (created_at)、型・長さ・null 許容を明示

複合インデックス例

2-3 インデックス・ユニーク制約設計指針

  1. 検索頻度が高い列@Index()(シングル)または @Index(['colA','colB'])(複合)。
  2. ビジネス上の重複禁止@Unique()@Column({ unique: true })
  3. 実行計画の確認EXPLAIN ANALYZE をローカル DB で走らせ、インデックスが利用されているか必ず検証。

2-4 まとめ(Key Takeaways)

  • エンティティは単数形・テーブルは複数形のスネークケースで統一。
  • カラムは型・長さ・nullable を明示し、不要なオプションは省くことでマイグレーション差分を最小化。
  • インデックスは「検索頻度」と「クエリ構造」を基に設計し、実行計画で効果検証。

3. リポジトリ & サービス層の分離パターン

3-1 概要

永続化ロジックとビジネスロジックを混在させるとテストが困難になるだけでなく、将来別 ORM に置き換える際に大規模なリファクタリングが必要になります。本章では Domain‑Repository インターフェイスNest DI コンテナによる実装注入 の手順を示します。

3-2 ドメインインターフェイス定義

3-3 TypeORM 実装

3-4 サービス層(ビジネスロジック)

3-5 モジュールでの DI 設定

3-6 テストでのモック置換例

3-7 まとめ(Key Takeaways)

  • インターフェイスで抽象化 → 実装切替が容易。
  • DI コンテナにトークン登録 ('IUserRepository') でサービス層から永続化ロジックを完全に分離。
  • テストはリポジトリモックだけ差し替えれば、DB に依存しない高速ユニットテストが実現。

4. トランザクション管理とマイグレーション運用フロー

4-1 概要

複数テーブルに跨る書き込みは トランザクション で保護しなければデータ不整合が発生します。また、スキーマ変更は マイグレーションファイル として管理することで、チーム全体のデプロイを安全に行えます。本章では デコレータベーストランザクションQueryRunner 手動制御 の使い分け、さらに CI に組み込むマイグレーション自動化手順を紹介します。

4-2 @Transaction デコレータのシンプルな利用

  • メリット:メソッド単位で自動的に BEGIN → COMMIT / ROLLBACK が走り、コードが非常にシンプル。
  • 制限:複数リポジトリや外部 API 呼び出しを跨ぐ場合は手動制御の方が柔軟。

4-3 QueryRunner を用いた手動トランザクション

  • ポイント:開始・コミット・ロールバックをコードで明示的に制御できるため、外部サービス呼び出しや複数リポジトリ操作が混在するケースに最適。

4-4 マイグレーションの自動生成と CI 統合

  1. スキーマ変更 → マイグレーション自動生成
    bash
    npx typeorm migration:generate -n AddUserProfile
  2. ローカルで実行・検証
    bash
    npm run migration:run # package.json に以下を追加しておくと便利
  3. package.json のスクリプト例

  1. GitHub Actions に組み込む例

  • 失敗時の挙動:マイグレーションがエラーになるとジョブが即座に停止し、デプロイは保留されます。

4-5 まとめ(Key Takeaways)

シナリオ 推奨手法
単一メソッドで完結する DB 書き込み @Transaction デコレータ
複数リポジトリ・外部 API を跨ぐ複雑フロー QueryRunner 手動制御
スキーマ変更のチーム共有 typeorm migration:generate → CI の migration:run

5. パフォーマンス最適化と接続プール/ソフトデリート戦略

5-1 概要

本番環境では 接続プール が過不足するとスループットが変動し、N+1 問題 が顕在化するとレスポンスが急激に遅くなります。また、削除データを保持したいケースは ソフトデリート を導入するのが一般的です。本章では環境別プール設定例、eager vs lazy の選択指針、QueryBuilder での N+1 回避策、そして @DeleteDateColumn によるアーカイブ戦略をまとめます。

5-2 接続プールの環境別チューニング

環境 max 推奨値 コメント
development 5 ローカルリソース節約
staging 10~15 本番に近い負荷テスト用
production 20~30 高同時接続数に対応

5-3 eager vs lazy の選択指針

条件 推奨
常に取得したい親子関係(例: 投稿 → 作者) @ManyToOne(..., { eager: true })
大量レコード・必要時のみ取得したい場合 lazy (Promise<Relation[]>) + 明示的な QueryBuilder 結合

実装例

5-4 QueryBuilder で N+1 を防ぐパターン

  • ポイントleftJoinAndSelect により関連エンティティを一括取得し、ループ内で個別 SELECT が走らないようにします。

5-5 ソフトデリート実装

  • 削除repo.softRemove(entity) または repo.delete(id)(実際には softDelete)で deleted_at にタイムスタンプが入ります。
  • 取得時のスコープ

  • アーカイブバッチ例(cron)

5-6 まとめ(Key Takeaways)

  • 接続プールは環境に合わせて max を調整し、アイドルタイムアウトは一定に保つ。
  • eager/lazy の選択は「取得頻度」と「レコードサイズ」で判断し、必要なら QueryBuilder で明示的に結合。
  • N+1 回避leftJoinAndSelect とページングの併用が基本。
  • ソフトデリート@DeleteDateColumn + スコープクエリで実装し、定期バッチで物理削除を行う。

6. テスト環境での TypeORM 活用とエラーハンドリング/バリデーション統合

6-1 概要

本番コードと同一 ORM をテストに流用すれば、実装ミスマッチが減ります。軽量な SQLite インメモリ DB を利用しつつ、リポジトリインターフェイスのモックclass‑validator/ExceptionFilter の共通化で、信頼性の高いユニットテストと統合テストを実装できます。

6-2 SQLite インメモリ DB 設定

  • synchronize: true はテスト専用オプションで、マイグレーション不要の高速セットアップを実現します。

6-3 リポジトリモックのベストプラクティス

  • ポイント:インターフェイス単位でモックを提供すれば、DB 接続が全く不要な純粋ユニットテストになる。

6-4 class-validator と class-transformer のグローバルパイプ

DTO の例

6-5 グローバル例外フィルター

6-6 テストでのバリデーション・例外ハンドリング確認

6-7 まとめ(Key Takeaways)

項目 実装ポイント
DB SQLite :memory: + synchronize:true でテスト環境を即時構築。
リポジトリ インターフェイス注入 → Jest のモックオブジェクトだけ差し替えれば DB 依存なし。
バリデーション グローバル ValidationPipe が全コントローラに適用され、テストでも同様に走る。
例外処理 AllExceptionsFilter で未捕捉エラーも JSON 統一形式に変換し、フロントが期待する形を保証。

おわりに

本稿では NestJS + TypeORM の実務的な設定・設計からテストまで、一連の流れを階層化した見出し構造で整理しました。以下のチェックリストをプロジェクトに当てはめるだけで、安全かつスケーラブル なバックエンドが完成します。

  1. 環境別 .envConfigModule.forRoot({ envFilePath }) が正しく機能しているか。
  2. DataSource プロバイダー がトークン化され、どこからでも注入できるか。
  3. エンティティの命名・インデックスが統一されているか。
  4. リポジトリはインターフェイスで抽象化し、DI で実装を切り替えているか。
  5. トランザクションは @TransactionQueryRunner を使い分け、マイグレーションが CI に組み込まれているか。
  6. 接続プール・eager‑lazy の選択と N+1 回避策が実装されているか。
  7. ソフトデリートと定期アーカイブバッチが整備されているか。
  8. テストは SQLite インメモリで走り、リポジトリモック・グローバルパイプ・例外フィルターが統一的に扱われているか。

これらを順守すれば、保守性・拡張性・信頼性 の高い NestJS アプリケーションが実現できます。ぜひ本ガイドをリファレンスとして、プロジェクトに即座に適用してください。 🚀

スポンサードリンク

-NestJS