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/typeorm、typeorm、mysql2のインストール
  2. 接続設定: TypeOrmModule.forRoot()とforRootAsync()による設定
  3. エンティティ定義: デコレータを使用したエンティティクラスの作成
  4. リポジトリパターン: @InjectRepository()によるCRUD操作の実装
  5. マイグレーション: スキーマ変更を安全に管理する方法

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

参考リンク