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操作を実装します。
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:revert
|
接続テストと動作確認#
設定が正しく行われているか確認するため、アプリケーションを起動してテストします。
アプリケーションの起動#
正常に接続できた場合、以下のようなログが出力されます。
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データベースに接続する方法を解説しました。
学習した内容を振り返ります。
- パッケージ導入:
@nestjs/typeorm、typeorm、mysql2のインストール
- 接続設定:
TypeOrmModule.forRoot()とforRootAsync()による設定
- エンティティ定義: デコレータを使用したエンティティクラスの作成
- リポジトリパターン:
@InjectRepository()によるCRUD操作の実装
- マイグレーション: スキーマ変更を安全に管理する方法
次のステップとして、エンティティ間のリレーション(@OneToMany、@ManyToOneなど)やトランザクション管理について学ぶことで、より複雑なデータモデルを扱えるようになります。
参考リンク#