jpskill.com
🛠️ 開発・MCP コミュニティ

api-database-typeorm

TypeScriptで記述されたアプリケーションにおいて、Active RecordやData Mapperといったデザインパターンを用いて、データベースとの連携を容易にするORM(Object-Relational Mapper)を、デコレーターベースで実現するSkill。

📜 元の英語説明(参考)

Decorator-based ORM for TypeScript with Active Record and Data Mapper patterns

🇯🇵 日本人クリエイター向け解説

一言でいうと

TypeScriptで記述されたアプリケーションにおいて、Active RecordやData Mapperといったデザインパターンを用いて、データベースとの連携を容易にするORM(Object-Relational Mapper)を、デコレーターベースで実現するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o api-database-typeorm.zip https://jpskill.com/download/10237.zip && unzip -o api-database-typeorm.zip && rm api-database-typeorm.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/10237.zip -OutFile "$d\api-database-typeorm.zip"; Expand-Archive "$d\api-database-typeorm.zip" -DestinationPath $d -Force; ri "$d\api-database-typeorm.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して api-database-typeorm.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → api-database-typeorm フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

TypeORM を使用したデータベース

クイックガイド: 完全な TypeScript サポートを備えたデコレータベースのデータベースアクセスには TypeORM を使用します。スキーマは、@Entity@Column@PrimaryGeneratedColumn を使用したエンティティクラスを介して定義されます。自明でないアプリケーションには、Active Record よりも Data Mapper パターン(リポジトリ)を使用します。本番環境では synchronize: true を絶対に使用しないでください - マイグレーションを使用してください。操作の種類がわかっている場合は、save() よりも insert()/update() を優先します - save() は常に最初に SELECT を実行します。完全に制御するには、QueryRunner トランザクションを使用します。Eager リレーションは、QueryBuilder ではなく find* メソッドでのみ機能します。


<critical_requirements>

重要: この Skill を使用する前に

すべてのコードは CLAUDE.md のプロジェクト規約に従う必要があります (kebab-case、名前付きエクスポート、インポート順序、import type、名前付き定数)

(本番環境では synchronize: true を絶対に使用しないでください - エンティティが変更されると、カラムが削除され、データが失われる可能性があります)

(操作の種類がわかっている場合は、save() の代わりに insert()/update() を使用する必要があります - save() は常に余分な SELECT クエリを実行します)

(提供されたトランザクション manager パラメータまたはトランザクション内の queryRunner.manager を使用する必要があります - グローバルエンティティマネージャーまたはリポジトリを絶対に使用しないでください)

(リレーションは、@OneToOne の所有側で明示的な @JoinColumn() を使用して定義し、必要に応じて @ManyToOne を使用し、@ManyToMany の片側で @JoinTable() を使用して定義する必要があります)

</critical_requirements>


自動検出: typeorm, TypeORM, DataSource, @Entity, @Column, @PrimaryGeneratedColumn, @ManyToOne, @OneToMany, @ManyToMany, createQueryBuilder, getRepository, EntityManager, QueryRunner, migration:generate, migration:run

使用する場合:

  • TypeScript を使用したデコレータベースのエンティティ定義
  • Active Record と Data Mapper パターンの両方を必要とするアプリケーション
  • join およびサブクエリを使用した QueryBuilder を必要とする複雑なクエリ
  • クラスベースの ORM が自然に感じられるプロジェクト (特に DI ベースのフレームワーク)

使用しない場合:

  • スキーマファーストのワークフロー (代わりにスキーマファーストの ORM を検討してください)
  • ランタイムデコレータなしで完全にタイプセーフなクエリが必要な場合 (より軽量な ORM を検討してください)
  • コールドスタートを最小限に抑えた Edge/serverless (デコレータメタデータが重くなる)
  • reflect-metadataexperimentalDecorators を回避するプロジェクト

カバーされる主要なパターン:

  • DataSource の構成とエンティティの登録
  • デコレータとカラム型を使用したエンティティ定義
  • リレーション (OneToOne、OneToMany、ManyToOne、ManyToMany)
  • リポジトリの CRUD と QueryBuilder
  • マイグレーション (生成、実行、ロールバック)
  • トランザクション (EntityManager コールバック、QueryRunner 手動)
  • save()insert()/update() のパフォーマンス

詳細なリソース:

  • examples/core.md - DataSource のセットアップ、エンティティ、CRUD、リポジトリパターン
  • examples/relations.md - すべてのリレーションタイプ、Eager/Lazy ローディング、カスケード
  • examples/query-builder.md - Join、サブクエリ、ページネーション、Raw クエリ
  • examples/migrations.md - 生成、実行、ロールバック、CLI 構成
  • examples/transactions.md - EntityManager、QueryRunner、分離レベル
  • examples/advanced.md - サブスクライバー、リスナー、ツリーエンティティ、埋め込みエンティティ
  • reference.md - 意思決定フレームワーク、アンチパターン、パフォーマンス、チェックリスト

<philosophy>

哲学

TypeORM は TypeScript デコレータを使用して、データベースエンティティをクラスとして定義します。Active Record と Data Mapper パターンの両方をサポートしており、チームはデータアクセスをどのように構成するかを柔軟に選択できます。

コア原則:

  1. デコレータベースのスキーマ - エンティティは、@Entity@Column などで装飾されたクラスです。
  2. パターンの柔軟性 - シンプルさのための Active Record、関心の分離のための Data Mapper
  3. QueryBuilder のパワー - 単純な find* を超えた複雑なクエリのための SQL のような Fluent API
  4. マイグレーション駆動 - バージョン管理されたマイグレーションファイルによるスキーマ変更、本番環境での自動同期は絶対に行わない

Active Record 対 Data Mapper:

  • Active Record: エンティティは BaseEntity を拡張し、User.find()user.save() を直接呼び出します。小規模なアプリや迅速なプロトタイピングに適しています。
  • Data Mapper: エンティティはプレーンなクラスであり、リポジトリが永続化を処理します (userRepo.find()userRepo.save())。複雑なアプリ、テスト、関心の分離に適しています。

推奨事項: 自明でないアプリケーションには Data Mapper を使用してください。Active Record はドメインロジックを永続化に結合するため、テストとリファクタリングが難しくなります。

</philosophy>


<patterns>

コアパターン

パターン 1: DataSource の構成

DataSource をシングルトンとして構成します。アプリケーションとマイグレーション CLI の両方のためにエクスポートします。

// data-source.ts
import { DataSource } from "typeorm";
import { User } from "./entities/user.entity";
import { Post } from "./entities/post.entity";

export const AppDataSource = new DataSource({
  type: "postgres",
  host: process.env.DB_HOST,
  port: Number(process.env.DB_PORT),
  username: process.env.DB_USER,
  password: process.env.DB_PASS,
  database: process.env.DB_NAME,
  entities: [User, Post],
  migrations: ["./src/migrations/*.ts"],
  synchronize: false, // NEVER true in production
  logging: process.env.NODE_ENV === "development",
});

良い理由: アプリと CLI の両方で使用される単一の DataSource エクスポート、synchronize: false はデータ損失を防ぎ、構成には環境変数を使用

// BAD: synchronize in production
const AppDataSource = new DataSource({
  synchronize: true, // Drops columns, loses data on entity changes
  entities: ["./src/**/*.entity.ts"], // Glob patterns are fragile
});

悪い理由: synchronize: true は起動時にスキーマを変更します (データを含むカラムを削除する可能性があります)、glob エンティティパスはバンドラーで壊れ、非決定的です

初期化、グレースフルシャットダウン、エンティティ登録パターンについては、examples/core.md を参照してください。


パターン 2: エンティティ定義

エンティティは、データベーステーブルと co にマッピングするデコレータを持つクラスです

(原文がここで切り詰められています)

📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Database with TypeORM

Quick Guide: Use TypeORM for decorator-based database access with full TypeScript support. Schema defined via entity classes with @Entity, @Column, @PrimaryGeneratedColumn. Use Data Mapper pattern (repositories) over Active Record for non-trivial apps. Never use synchronize: true in production - use migrations. Prefer insert()/update() over save() when you know the operation type - save() always executes a SELECT first. Use QueryRunner transactions for full control. Eager relations only work with find* methods, not QueryBuilder.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST NEVER use synchronize: true in production - it can drop columns and lose data when entities change)

(You MUST use insert()/update() instead of save() when the operation type is known - save() always runs an extra SELECT query)

(You MUST use the provided transaction manager parameter or queryRunner.manager inside transactions - NEVER use the global entity manager or repository)

(You MUST define relations with explicit @JoinColumn() on the owning side of @OneToOne and optionally @ManyToOne, and @JoinTable() on one side of @ManyToMany)

</critical_requirements>


Auto-detection: typeorm, TypeORM, DataSource, @Entity, @Column, @PrimaryGeneratedColumn, @ManyToOne, @OneToMany, @ManyToMany, createQueryBuilder, getRepository, EntityManager, QueryRunner, migration:generate, migration:run

When to use:

  • Decorator-based entity definitions with TypeScript
  • Applications requiring both Active Record and Data Mapper patterns
  • Complex queries needing QueryBuilder with joins and subqueries
  • Projects where class-based ORM feels natural (especially with DI-based frameworks)

When NOT to use:

  • Schema-first workflows (consider schema-first ORMs instead)
  • Needing fully type-safe queries without runtime decorators (consider lighter ORMs)
  • Edge/serverless with minimal cold start (decorator metadata adds weight)
  • Projects avoiding reflect-metadata and experimentalDecorators

Key patterns covered:

  • DataSource configuration and entity registration
  • Entity definitions with decorators and column types
  • Relations (OneToOne, OneToMany, ManyToOne, ManyToMany)
  • Repository CRUD and QueryBuilder
  • Migrations (generate, run, revert)
  • Transactions (EntityManager callback, QueryRunner manual)
  • save() vs insert()/update() performance

Detailed Resources:


<philosophy>

Philosophy

TypeORM uses TypeScript decorators to define database entities as classes. It supports both the Active Record and Data Mapper patterns, giving teams flexibility in how they structure data access.

Core principles:

  1. Decorator-based schema - Entities are classes decorated with @Entity, @Column, etc.
  2. Pattern flexibility - Active Record for simplicity, Data Mapper for separation of concerns
  3. QueryBuilder power - SQL-like fluent API for complex queries beyond simple find*
  4. Migration-driven - Schema changes through versioned migration files, never auto-sync in production

Active Record vs Data Mapper:

  • Active Record: Entities extend BaseEntity, call User.find(), user.save() directly. Good for small apps and rapid prototyping.
  • Data Mapper: Entities are plain classes, repositories handle persistence (userRepo.find(), userRepo.save()). Better for complex apps, testing, and separation of concerns.

Recommendation: Use Data Mapper for any non-trivial application. Active Record couples domain logic to persistence, making testing and refactoring harder.

</philosophy>


<patterns>

Core Patterns

Pattern 1: DataSource Configuration

Configure the DataSource as a singleton. Export it for both the application and migration CLI.

// data-source.ts
import { DataSource } from "typeorm";
import { User } from "./entities/user.entity";
import { Post } from "./entities/post.entity";

export const AppDataSource = new DataSource({
  type: "postgres",
  host: process.env.DB_HOST,
  port: Number(process.env.DB_PORT),
  username: process.env.DB_USER,
  password: process.env.DB_PASS,
  database: process.env.DB_NAME,
  entities: [User, Post],
  migrations: ["./src/migrations/*.ts"],
  synchronize: false, // NEVER true in production
  logging: process.env.NODE_ENV === "development",
});

Why good: Single DataSource export used by both app and CLI, synchronize: false prevents data loss, env vars for config

// BAD: synchronize in production
const AppDataSource = new DataSource({
  synchronize: true, // Drops columns, loses data on entity changes
  entities: ["./src/**/*.entity.ts"], // Glob patterns are fragile
});

Why bad: synchronize: true alters schema on startup (can drop columns with data), glob entity paths break with bundlers and are non-deterministic

See examples/core.md for initialization, graceful shutdown, and entity registration patterns.


Pattern 2: Entity Definition

Entities are classes with decorators mapping to database tables and columns.

import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  Index,
} from "typeorm";

@Entity("users") // Explicit table name
export class User {
  @PrimaryGeneratedColumn("uuid")
  id: string;

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

  @Column()
  name: string;

  @Column({ type: "enum", enum: ["user", "admin"], default: "user" })
  role: string;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}

Why good: Explicit table name avoids casing issues, uuid for distributed-safe IDs, CreateDateColumn/UpdateDateColumn auto-managed by TypeORM, enum column with default

// BAD: Missing explicit table name, no index on frequently queried column
@Entity() // Table name derived from class name - casing varies by database
export class UserProfile {
  @PrimaryGeneratedColumn() // Auto-increment integer - problematic for distributed systems
  id: number;

  @Column()
  userId: string; // No index, no foreign key relation defined
}

Why bad: Derived table names cause casing inconsistency across databases, auto-increment IDs conflict in distributed systems, missing indexes on lookup columns

See examples/core.md for column types, nullable columns, and default values.


Pattern 3: Repository CRUD - save() vs insert()/update()

The critical performance distinction: save() always runs a SELECT first. Use insert()/update() when you know the operation.

const userRepo = AppDataSource.getRepository(User);

// CREATING: Use insert() - single INSERT query
await userRepo.insert({
  email: "alice@example.com",
  name: "Alice",
});

// UPDATING: Use update() - single UPDATE query
const ACTIVE_ROLE = "admin";
await userRepo.update({ id: userId }, { role: ACTIVE_ROLE });

// UPSERTING: Use upsert() - INSERT ... ON CONFLICT
await userRepo.upsert(
  { email: "alice@example.com", name: "Alice Updated" },
  ["email"], // conflict columns
);

// save() - only when you need cascade saves or don't know if inserting/updating
const user = userRepo.create({ email: "bob@example.com", name: "Bob" });
await userRepo.save(user); // SELECT + INSERT (2 queries)

Why good: insert()/update() execute single queries, upsert() handles conflicts atomically, save() reserved for when cascades or ambiguous operations are needed

// BAD: Using save() for everything
const user = new User();
user.email = "alice@example.com";
user.name = "Alice";
await userRepo.save(user); // Runs SELECT first, then INSERT - 2 round trips

// BAD: Using save() in a loop
for (const data of users) {
  await userRepo.save(data); // 2N queries instead of 1 bulk insert
}

Why bad: save() always runs SELECT + INSERT/UPDATE (2 round trips), in loops this becomes 2N queries; use insert() for bulk creates

See examples/core.md for find operations, bulk operations, and soft delete patterns.


Pattern 4: Relations

Define relations with decorators. The owning side holds the foreign key.

import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  ManyToOne,
  OneToMany,
  JoinColumn,
} from "typeorm";

@Entity("posts")
export class Post {
  @PrimaryGeneratedColumn("uuid")
  id: string;

  @Column()
  title: string;

  // Owning side - holds the foreign key column
  @ManyToOne(() => User, (user) => user.posts, { onDelete: "CASCADE" })
  @JoinColumn({ name: "author_id" }) // Explicit FK column name
  author: User;

  @Column()
  authorId: string; // Expose FK for queries without joining
}

@Entity("users")
export class User {
  @PrimaryGeneratedColumn("uuid")
  id: string;

  // Inverse side - no FK column here
  @OneToMany(() => Post, (post) => post.author)
  posts: Post[];
}

Why good: Explicit @JoinColumn names the FK column, authorId exposed for direct queries, onDelete: "CASCADE" prevents orphans, inverse side defined for bidirectional navigation

// BAD: Missing JoinColumn, no onDelete, relation typed as required
@ManyToOne(() => User)
author: User; // No explicit FK column name, no cascade delete

Why bad: Auto-generated FK column name may not match conventions, missing onDelete leaves orphaned rows, relation property should be User | undefined since it's not always loaded

See examples/relations.md for all relation types, ManyToMany with JoinTable, and eager/lazy loading.


Pattern 5: QueryBuilder

For queries beyond simple find*, use the QueryBuilder's fluent API.

const DEFAULT_PAGE_SIZE = 20;
const MAX_PAGE_SIZE = 100;

const users = await AppDataSource.getRepository(User)
  .createQueryBuilder("user")
  .leftJoinAndSelect("user.posts", "post", "post.published = :pub", {
    pub: true,
  })
  .where("user.role = :role", { role: "admin" })
  .andWhere("user.createdAt > :date", { date: new Date("2024-01-01") })
  .orderBy("user.createdAt", "DESC")
  .take(DEFAULT_PAGE_SIZE)
  .skip(0)
  .getMany();

Why good: Parameterized queries prevent SQL injection, leftJoinAndSelect loads relations in one query, take/skip for pagination (relation-safe unlike limit/offset)

// BAD: String interpolation in where clause
const users = await userRepo
  .createQueryBuilder("user")
  .where(`user.email = '${email}'`) // SQL INJECTION!
  .getMany();

Why bad: String interpolation opens SQL injection vulnerability; always use :paramName with parameter objects

See examples/query-builder.md for subqueries, aggregations, raw queries, and advanced joins.


Pattern 6: Migrations

Generate migrations from entity changes, never manually write SQL unless necessary.

# Generate migration from entity diff
npx typeorm-ts-node-esm migration:generate ./src/migrations/AddUserRole -d ./src/data-source.ts

# Run all pending migrations
npx typeorm-ts-node-esm migration:run -d ./src/data-source.ts

# Revert last migration
npx typeorm-ts-node-esm migration:revert -d ./src/data-source.ts

Why good: Auto-generated migrations capture exact schema diff, -d flag points to DataSource config, revert undoes one migration at a time

See examples/migrations.md for migration class structure, manual migrations, and transaction control.


Pattern 7: Transactions

Two approaches: EntityManager callback (simple) and QueryRunner (full control).

// Approach 1: EntityManager callback - simple, auto-commits/rollbacks
await AppDataSource.transaction(async (manager) => {
  await manager.save(User, userData);
  await manager.save(Post, postData);
  // If any operation throws, entire transaction rolls back
});

// Approach 2: QueryRunner - manual control, reusable connection
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
  await queryRunner.manager.save(User, userData);
  await queryRunner.manager.save(Post, postData);
  await queryRunner.commitTransaction();
} catch (error) {
  await queryRunner.rollbackTransaction();
  throw error;
} finally {
  await queryRunner.release(); // ALWAYS release
}

Why good: EntityManager callback is concise with auto-rollback, QueryRunner gives explicit commit/rollback control, finally block ensures connection release

// BAD: Using global manager inside transaction
await AppDataSource.transaction(async (manager) => {
  await AppDataSource.manager.save(User, userData); // WRONG: bypasses transaction!
  await manager.save(Post, postData);
});

Why bad: AppDataSource.manager is the global manager, not the transactional one - operations using it run outside the transaction and won't roll back

See examples/transactions.md for isolation levels, QueryRunner patterns, and nested transactions.

</patterns>


<red_flags>

RED FLAGS

High Priority Issues:

  • synchronize: true in production - alters schema on startup, can drop columns and lose data
  • Using save() for all writes - always runs SELECT first, 2x round trips for known inserts/updates
  • String interpolation in QueryBuilder .where() - SQL injection vulnerability
  • Using global entity manager inside transactions - bypasses transaction context
  • Missing queryRunner.release() in finally block - leaks database connections

Medium Priority Issues:

  • No indexes on frequently filtered columns - slow queries as data grows
  • Missing onDelete cascade on relations - orphaned rows when parent deleted
  • Using eager: true on both sides of a relation - TypeORM disallows this, throws error
  • Glob patterns for entity paths ("./src/**/*.entity.ts") - breaks with bundlers
  • Initializing relation arrays with = [] - causes TypeORM to detach all existing relations on save

Common Mistakes:

  • Expecting eager relations to work with QueryBuilder - eager only works with find* methods, use leftJoinAndSelect instead
  • Using @BeforeUpdate/@AfterUpdate with update() - listeners only fire with save(), not update()/insert()
  • Forgetting reflect-metadata import at app entry point - decorators silently fail
  • Using limit()/offset() with joins in QueryBuilder - returns wrong results; use take()/skip() instead
  • Not exposing FK column (e.g., authorId) alongside relation - forces a join for simple lookups

Gotchas & Edge Cases:

  • save() returns the saved entity but reloads it from DB - the returned object may differ from input
  • update() and delete() return UpdateResult/DeleteResult with affected count, not the entity
  • findOne({ where: {} }) with empty where returns the first row, not null - always provide conditions
  • Enum changes in entity require a migration - database enum types don't auto-update
  • @Column({ select: false }) excludes column from default SELECTs - must explicitly select with QueryBuilder
  • Lazy relations require Promise<T> type on the property - not intuitive for JS/TS developers
  • cascade: true can save unintended nested objects - be explicit with cascade: ["insert"] or cascade: ["update"]
  • Transaction isolation varies by database driver - not all levels available on all databases
  • QueryRunner must be released even on success - failure to release leaks connections until pool exhaustion

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md

(You MUST NEVER use synchronize: true in production - it can drop columns and lose data when entities change)

(You MUST use insert()/update() instead of save() when the operation type is known - save() always runs an extra SELECT query)

(You MUST use the provided transaction manager parameter or queryRunner.manager inside transactions - NEVER use the global entity manager or repository)

(You MUST define relations with explicit @JoinColumn() on the owning side of @OneToOne and optionally @ManyToOne, and @JoinTable() on one side of @ManyToMany)

Failure to follow these rules will cause data loss from schema sync, doubled query counts from unnecessary SELECTs, broken transaction atomicity, and connection pool exhaustion.

</critical_reminders>