NestJSは、TypeORMとのシームレスな統合を提供する@nestjs/typeormパッケージを公式にサポートしています。TypeORMはTypeScriptで書かれた成熟したORM(Object-Relational Mapping)であり、エンティティベースのデータモデリング、リポジトリパターン、マイグレーションなど、本格的なデータベース操作に必要な機能を備えています。本記事では、NestJSプロジェクトにTypeORMを導入し、MySQLデータベースへの接続からエンティティ定義、マイグレーションの基本までを実践的に解説します。

実行環境と前提条件

本記事の内容を実践するにあたり、以下の環境を前提としています。

項目 バージョン・要件
Node.js 20以上
npm 10以上
NestJS 11.x
TypeORM 0.3.x
MySQL 8.0以上
OS Windows / macOS / Linux
エディタ VS Code(推奨)

事前に以下の準備を完了してください。

  • NestJS CLIのインストール済み(npm install -g @nestjs/cli
  • NestJSプロジェクトの作成済み(nest newコマンドで作成可能)
  • MySQLサーバーが起動しており、接続可能な状態

プロジェクトの作成方法はNestJS入門記事を参照してください。

TypeORMとは

TypeORMは、TypeScriptおよびJavaScript向けに設計されたORMライブラリです。データベースとオブジェクト指向プログラミングの間のインピーダンスミスマッチを解消し、型安全なデータベース操作を実現します。

TypeORMの主な特徴

特徴 説明
複数データベース対応 MySQL、PostgreSQL、SQLite、Oracle、SQL Serverなど多数対応
デコレータベース エンティティ定義にTypeScriptデコレータを使用
2つのパターン Active RecordとData Mapper両方のパターンをサポート
マイグレーション スキーマ変更を追跡・適用するマイグレーション機能
QueryBuilder 複雑なクエリを型安全に構築可能

NestJSとTypeORMの統合

NestJSは@nestjs/typeormパッケージを通じてTypeORMとの緊密な統合を提供しています。この統合により以下のメリットが得られます。

  • モジュール式の設定: TypeOrmModule.forRoot()でアプリケーション全体の接続設定
  • リポジトリの自動注入: @InjectRepository()デコレータによるDI対応
  • テスタビリティ: モックリポジトリへの容易な差し替え
  • 非同期設定: 環境変数や外部設定からの動的な接続設定

パッケージのインストール

TypeORMをNestJSプロジェクトで使用するために、必要なパッケージをインストールします。

1
npm install --save @nestjs/typeorm typeorm mysql2

各パッケージの役割は以下のとおりです。

パッケージ 役割
@nestjs/typeorm NestJSとTypeORMを統合するラッパーモジュール
typeorm TypeORM本体
mysql2 MySQLへの接続ドライバ

TypeOrmModule.forRoot()による接続設定

データベース接続は、アプリケーションのルートモジュール(AppModule)でTypeOrmModule.forRoot()を使用して設定します。

基本的な接続設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'password',
      database: 'nestjs_demo',
      entities: [],
      synchronize: true,
    }),
  ],
})
export class AppModule {}

接続オプションの詳細

TypeOrmModule.forRoot()に渡す主要なオプションを解説します。

オプション 説明
type string データベースの種類(mysql, postgresなど)
host string データベースサーバーのホスト名
port number 接続ポート番号(MySQLのデフォルトは3306)
username string 接続ユーザー名
password string 接続パスワード
database string 使用するデータベース名
entities array 読み込むエンティティクラスの配列
synchronize boolean エンティティとスキーマを自動同期(開発用)
autoLoadEntities boolean forFeature()で登録されたエンティティを自動読み込み

synchronizeオプションの注意点

synchronize: trueは、エンティティ定義の変更を自動的にデータベーススキーマに反映する便利な機能です。しかし、本番環境では絶対に使用しないでください。

1
2
3
4
5
// 開発環境での設定例
TypeOrmModule.forRoot({
  // ...他の設定
  synchronize: process.env.NODE_ENV !== 'production',
})

本番環境では、後述するマイグレーション機能を使用してスキーマを管理します。

環境変数による接続設定

ハードコーディングされた接続情報はセキュリティリスクとなります。@nestjs/configパッケージを使用して環境変数から設定を読み込む方法を解説します。

ConfigModuleの導入

1
npm install --save @nestjs/config

環境変数ファイルの作成

プロジェクトルートに.envファイルを作成します。

1
2
3
4
5
6
# .env
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=password
DB_DATABASE=nestjs_demo

.gitignore.envを追加して、バージョン管理から除外してください。

forRootAsync()による非同期設定

TypeOrmModule.forRootAsync()を使用して、ConfigServiceから設定を注入します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        type: 'mysql',
        host: configService.get<string>('DB_HOST'),
        port: configService.get<number>('DB_PORT'),
        username: configService.get<string>('DB_USERNAME'),
        password: configService.get<string>('DB_PASSWORD'),
        database: configService.get<string>('DB_DATABASE'),
        autoLoadEntities: true,
        synchronize: configService.get<string>('NODE_ENV') !== 'production',
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

この設定により、環境ごとに異なるデータベース接続情報を柔軟に切り替えられます。

Entityクラスの定義

エンティティは、データベーステーブルにマッピングされるクラスです。TypeORMではデコレータを使用してエンティティを定義します。

ユーザーエンティティの作成

ユーザー管理機能を例に、エンティティを作成します。

1
2
# ディレクトリ構造の作成
mkdir -p src/users
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// src/users/user.entity.ts
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
} from 'typeorm';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 100 })
  firstName: string;

  @Column({ length: 100 })
  lastName: string;

  @Column({ unique: true })
  email: string;

  @Column({ default: true })
  isActive: boolean;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}

主要なデコレータの解説

デコレータ 説明
@Entity() クラスをエンティティとして定義。引数でテーブル名を指定可能
@PrimaryGeneratedColumn() 自動インクリメントの主キーカラム
@Column() 通常のカラム。オプションで型や制約を指定
@CreateDateColumn() レコード作成時に自動で日時を設定
@UpdateDateColumn() レコード更新時に自動で日時を更新

カラムオプションの詳細

@Column()デコレータには多様なオプションを指定できます。

1
2
3
4
5
6
7
8
@Column({
  type: 'varchar',      // カラム型(省略時は推論)
  length: 255,          // 文字列長
  nullable: false,      // NULL許可(デフォルトはfalse)
  unique: true,         // ユニーク制約
  default: 'default',   // デフォルト値
  select: false,        // findでデフォルト非取得(パスワード等に有効)
})

モジュールへのエンティティ登録

エンティティをNestJSモジュールに登録し、リポジトリを使用可能にします。

UsersModuleの作成

1
2
3
nest generate module users
nest generate service users
nest generate controller users
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsersService],
  controllers: [UsersController],
  exports: [UsersService],
})
export class UsersModule {}

TypeOrmModule.forFeature()は、現在のスコープで使用するリポジトリを登録します。これにより、UsersModule内でUserエンティティのリポジトリを注入できます。

AppModuleへのインポート

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        type: 'mysql',
        host: configService.get<string>('DB_HOST'),
        port: configService.get<number>('DB_PORT'),
        username: configService.get<string>('DB_USERNAME'),
        password: configService.get<string>('DB_PASSWORD'),
        database: configService.get<string>('DB_DATABASE'),
        autoLoadEntities: true,
        synchronize: configService.get<string>('NODE_ENV') !== 'production',
      }),
      inject: [ConfigService],
    }),
    UsersModule,
  ],
})
export class AppModule {}

リポジトリパターンによるデータ操作

TypeORMのリポジトリパターンを使用して、CRUD操作を実装します。

UsersServiceの実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// src/users/users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepository: Repository<User>,
  ) {}

  async findAll(): Promise<User[]> {
    return this.usersRepository.find();
  }

  async findOne(id: number): Promise<User> {
    const user = await this.usersRepository.findOneBy({ id });
    if (!user) {
      throw new NotFoundException(`User with ID ${id} not found`);
    }
    return user;
  }

  async create(userData: Partial<User>): Promise<User> {
    const user = this.usersRepository.create(userData);
    return this.usersRepository.save(user);
  }

  async update(id: number, userData: Partial<User>): Promise<User> {
    const user = await this.findOne(id);
    Object.assign(user, userData);
    return this.usersRepository.save(user);
  }

  async remove(id: number): Promise<void> {
    const user = await this.findOne(id);
    await this.usersRepository.remove(user);
  }
}

リポジトリの主要メソッド

メソッド 説明
find() 条件に一致する全レコードを取得
findOneBy() 条件に一致する最初のレコードを取得
create() エンティティインスタンスを作成(DBには保存しない)
save() エンティティをDBに保存(INSERT or UPDATE)
remove() エンティティをDBから削除
delete() 条件に一致するレコードを削除

UsersControllerの実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// src/users/users.controller.ts
import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Body,
  Param,
  ParseIntPipe,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { User } from './user.entity';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  findAll(): Promise<User[]> {
    return this.usersService.findAll();
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number): Promise<User> {
    return this.usersService.findOne(id);
  }

  @Post()
  create(@Body() userData: Partial<User>): Promise<User> {
    return this.usersService.create(userData);
  }

  @Put(':id')
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() userData: Partial<User>,
  ): Promise<User> {
    return this.usersService.update(id, userData);
  }

  @Delete(':id')
  remove(@Param('id', ParseIntPipe) id: number): Promise<void> {
    return this.usersService.remove(id);
  }
}

マイグレーションの基本

マイグレーションは、データベーススキーマの変更を追跡し、安全に適用するための仕組みです。本番環境ではsynchronizeの代わりにマイグレーションを使用します。

データソース設定ファイルの作成

TypeORM CLIはNestJSのモジュール設定を直接読み込めないため、専用のデータソース設定ファイルを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// src/data-source.ts
import { DataSource } from 'typeorm';
import { config } from 'dotenv';

config();

export const AppDataSource = new DataSource({
  type: 'mysql',
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT || '3306', 10),
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE,
  entities: ['src/**/*.entity.ts'],
  migrations: ['src/migrations/*.ts'],
  synchronize: false,
});

package.jsonへのスクリプト追加

マイグレーション操作を容易にするため、npmスクリプトを追加します。

1
2
3
4
5
6
7
8
{
  "scripts": {
    "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
    "migration:generate": "npm run typeorm -- migration:generate -d src/data-source.ts",
    "migration:run": "npm run typeorm -- migration:run -d src/data-source.ts",
    "migration:revert": "npm run typeorm -- migration:revert -d src/data-source.ts"
  }
}

tsconfig-pathsのインストール

1
npm install --save-dev tsconfig-paths

マイグレーションの生成

エンティティの変更に基づいてマイグレーションを自動生成します。

1
npm run migration:generate -- src/migrations/CreateUsersTable

生成されるマイグレーションファイルの例を示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/migrations/1704520800000-CreateUsersTable.ts
import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateUsersTable1704520800000 implements MigrationInterface {
  name = 'CreateUsersTable1704520800000';

  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE \`users\` (
        \`id\` int NOT NULL AUTO_INCREMENT,
        \`firstName\` varchar(100) NOT NULL,
        \`lastName\` varchar(100) NOT NULL,
        \`email\` varchar(255) NOT NULL,
        \`isActive\` tinyint NOT NULL DEFAULT 1,
        \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
        \`updatedAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
        UNIQUE INDEX \`IDX_email\` (\`email\`),
        PRIMARY KEY (\`id\`)
      ) ENGINE=InnoDB
    `);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`DROP TABLE \`users\``);
  }
}

マイグレーションの実行

1
npm run migration:run

マイグレーションのロールバック

直前のマイグレーションを取り消す場合は、以下のコマンドを実行します。

1
npm run migration:revert

接続テストと動作確認

設定が正しく行われているか確認するため、アプリケーションを起動してテストします。

アプリケーションの起動

1
npm run start:dev

正常に接続できた場合、以下のようなログが出力されます。

1
2
[Nest] LOG [TypeOrmModule] TypeOrm connected successfully
[Nest] LOG [NestApplication] Nest application successfully started

APIのテスト

curlコマンドまたはHTTPクライアントでエンドポイントをテストします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# ユーザー作成
curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"firstName":"Taro","lastName":"Yamada","email":"taro@example.com"}'

# ユーザー一覧取得
curl http://localhost:3000/users

# 特定ユーザー取得
curl http://localhost:3000/users/1

# ユーザー更新
curl -X PUT http://localhost:3000/users/1 \
  -H "Content-Type: application/json" \
  -d '{"firstName":"Jiro"}'

# ユーザー削除
curl -X DELETE http://localhost:3000/users/1

アーキテクチャの全体像

NestJSとTypeORMを統合したアプリケーションの構成を図示します。

graph TD
    A[Client] -->|HTTP Request| B[Controller]
    B -->|Call| C[Service]
    C -->|Query| D[Repository]
    D -->|SQL| E[(MySQL Database)]
    
    F[TypeOrmModule.forRoot] -->|Config| D
    G[Entity Definition] -->|Schema| E
    H[Migration] -->|DDL| E

本番環境に向けた推奨事項

本番環境でTypeORMを使用する際の推奨事項をまとめます。

項目 開発環境 本番環境
synchronize true(許容) false(必須)
スキーマ管理 自動同期 マイグレーション
接続情報 .envファイル 環境変数 or シークレット管理
コネクションプール デフォルト 負荷に応じて調整
ログ出力 詳細ログ エラーログのみ

コネクションプールの設定例

1
2
3
4
5
6
7
TypeOrmModule.forRoot({
  // ...他の設定
  extra: {
    connectionLimit: 10,  // 最大接続数
  },
  logging: ['error'],     // エラーのみログ出力
})

まとめ

本記事では、NestJSアプリケーションからTypeORMを使用してMySQLデータベースに接続する方法を解説しました。

学習した内容を振り返ります。

  1. パッケージ導入: @nestjs/typeormtypeormmysql2のインストール
  2. 接続設定: TypeOrmModule.forRoot()forRootAsync()による設定
  3. エンティティ定義: デコレータを使用したエンティティクラスの作成
  4. リポジトリパターン: @InjectRepository()によるCRUD操作の実装
  5. マイグレーション: スキーマ変更を安全に管理する方法

次のステップとして、エンティティ間のリレーション(@OneToMany@ManyToOneなど)やトランザクション管理について学ぶことで、より複雑なデータモデルを扱えるようになります。

参考リンク