typeorm

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TypeORM Core Knowledge

TypeORM 核心知识

Deep Knowledge: Use
mcp__documentation__fetch_docs
with technology:
typeorm
for comprehensive documentation.
深度知识:调用
mcp__documentation__fetch_docs
并指定技术为
typeorm
可获取完整文档。

When NOT to Use This Skill

不适用本技能的场景

  • Prisma Projects: Use
    prisma
    skill for Prisma-based applications
  • Drizzle Projects: Use
    drizzle
    skill for Drizzle ORM
  • Python Applications: Use
    sqlalchemy
    skill for Python ORMs
  • Raw SQL Operations: Use
    database-query
    MCP server for direct SQL
  • NoSQL Databases: Use
    mongodb
    skill for MongoDB (TypeORM MongoDB support is limited)
  • Database Design: Consult
    sql-expert
    or
    architect-expert
    for schema architecture
  • Migration Strategy: Engage
    devops-expert
    for production deployment planning
  • Prisma 项目:Prisma 技术栈应用请使用
    prisma
    技能
  • Drizzle 项目:Drizzle ORM 相关请使用
    drizzle
    技能
  • Python 应用:Python 技术栈 ORM 请使用
    sqlalchemy
    技能
  • 原生 SQL 操作:直接执行 SQL 请使用
    database-query
    MCP 服务
  • NoSQL 数据库:MongoDB 相关请使用
    mongodb
    技能(TypeORM 对 MongoDB 支持有限)
  • 数据库设计:库表架构设计请咨询
    sql-expert
    architect-expert
  • 迁移策略:生产部署规划请联系
    devops-expert

Entity Definition

实体定义

typescript
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, OneToMany } from 'typeorm';

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

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

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

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

  @CreateDateColumn()
  createdAt: Date;

  @OneToMany(() => Post, post => post.author)
  posts: Post[];
}

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column({ type: 'text', nullable: true })
  content: string;

  @ManyToOne(() => User, user => user.posts)
  author: User;
}
typescript
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, OneToMany } from 'typeorm';

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

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

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

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

  @CreateDateColumn()
  createdAt: Date;

  @OneToMany(() => Post, post => post.author)
  posts: Post[];
}

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column({ type: 'text', nullable: true })
  content: string;

  @ManyToOne(() => User, user => user.posts)
  author: User;
}

Repository Operations

存储库操作

typescript
import { AppDataSource } from './data-source';

const userRepository = AppDataSource.getRepository(User);

// Create
const user = userRepository.create({ name: 'John', email: 'john@example.com' });
await userRepository.save(user);

// Read
const users = await userRepository.find();
const user = await userRepository.findOneBy({ id: 1 });
const userWithPosts = await userRepository.findOne({
  where: { id: 1 },
  relations: { posts: true },
});

// Update
await userRepository.update(1, { name: 'Jane' });

// Delete
await userRepository.delete(1);
typescript
import { AppDataSource } from './data-source';

const userRepository = AppDataSource.getRepository(User);

// 创建
const user = userRepository.create({ name: 'John', email: 'john@example.com' });
await userRepository.save(user);

// 查询
const users = await userRepository.find();
const user = await userRepository.findOneBy({ id: 1 });
const userWithPosts = await userRepository.findOne({
  where: { id: 1 },
  relations: { posts: true },
});

// 更新
await userRepository.update(1, { name: 'Jane' });

// 删除
await userRepository.delete(1);

Query Builder

Query Builder

typescript
const users = await userRepository
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.posts', 'post')
  .where('user.isActive = :active', { active: true })
  .andWhere('post.published = :published', { published: true })
  .orderBy('user.createdAt', 'DESC')
  .take(10)
  .getMany();
typescript
const users = await userRepository
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.posts', 'post')
  .where('user.isActive = :active', { active: true })
  .andWhere('post.published = :published', { published: true })
  .orderBy('user.createdAt', 'DESC')
  .take(10)
  .getMany();

Data Source Config

数据源配置

typescript
import { DataSource } from 'typeorm';

export const AppDataSource = new DataSource({
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'user',
  password: 'password',
  database: 'mydb',
  entities: [User, Post],
  synchronize: false,
  migrations: ['src/migrations/*.ts'],
});
typescript
import { DataSource } from 'typeorm';

export const AppDataSource = new DataSource({
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'user',
  password: 'password',
  database: 'mydb',
  entities: [User, Post],
  synchronize: false,
  migrations: ['src/migrations/*.ts'],
});

Anti-Patterns

反模式

Anti-PatternWhy It's BadBetter Approach
synchronize: true
in production
Can drop tables, loses dataAlways use migrations in production
Using
find()
without relations
N+1 query problemUse
relations
option or QueryBuilder with joins
Not using transactions for multi-step opsData inconsistency riskWrap related operations in
transaction()
Hardcoded credentials in DataSourceSecurity vulnerabilityUse environment variables
No connection pool configurationConnection exhaustionSet
extra.max
and pool timeouts
Using
@Column()
without type for large text
May truncate dataSpecify
type: 'text'
for long content
Lazy loading relations everywherePerformance issues, N+1 queriesUse eager loading strategically
Not handling unique constraint errorsPoor error messages to usersCatch and handle constraint violations
Manual SQL without parametersSQL injection riskUse QueryBuilder with parameters
No indexes on frequently queried columnsSlow queriesAdd
@Index()
decorators
反模式存在的问题更优方案
生产环境开启
synchronize: true
可能删除表导致数据丢失生产环境始终使用迁移功能
使用
find()
时不指定关联关系
导致 N+1 查询问题使用
relations
选项或 Query Builder 关联查询
多步操作不使用事务存在数据不一致风险将关联操作包裹在
transaction()
数据源配置中硬编码凭证存在安全漏洞使用环境变量存储敏感信息
未配置连接池可能出现连接耗尽问题设置
extra.max
和连接池超时参数
大文本字段使用
@Column()
不指定类型
可能出现数据截断长内容指定
type: 'text'
所有关联关系都使用懒加载性能问题,N+1 查询策略性使用预加载
不处理唯一约束错误用户侧错误提示不友好捕获并处理约束违反异常
手动写 SQL 不使用参数存在 SQL 注入风险使用带参数的 Query Builder
高频查询字段未加索引查询速度慢添加
@Index()
装饰器

Quick Troubleshooting

快速排障

IssueLikely CauseSolution
"Cannot find name 'AppDataSource'"DataSource not initializedCall
AppDataSource.initialize()
at startup
"relation does not exist"Migration not runExecute pending migrations
"column does not exist"Entity/DB out of syncGenerate and run new migration
Type errors on entitiesDecorator metadata issueEnable
emitDecoratorMetadata
and
experimentalDecorators
in tsconfig
"Repository not found"Entity not registeredAdd entity to DataSource
entities
array
Slow queriesMissing indexes, no joinsAdd indexes, use
leftJoinAndSelect
Connection pool exhaustedToo many concurrent queriesIncrease
extra.max
pool size
"Cannot query across many-to-many"Missing join tableAdd explicit join table or use QueryBuilder
Migration generation creates no fileNo entity changes detectedManually create migration if needed
"ECONNREFUSED"Database not runningStart database, verify connection details
问题可能原因解决方案
"Cannot find name 'AppDataSource'"数据源未初始化应用启动时调用
AppDataSource.initialize()
"relation does not exist"迁移未执行执行待处理的迁移
"column does not exist"实体和数据库结构不同步生成并执行新的迁移
实体类型错误装饰器元数据配置问题在 tsconfig 中开启
emitDecoratorMetadata
experimentalDecorators
"Repository not found"实体未注册将实体添加到 DataSource 的
entities
数组
查询慢缺少索引、未使用关联查询添加索引,使用
leftJoinAndSelect
连接池耗尽并发查询过多调大
extra.max
连接池大小
"Cannot query across many-to-many"缺少中间关联表添加显式关联表或使用 Query Builder
迁移生成无文件未检测到实体变更必要时手动创建迁移
"ECONNREFUSED"数据库未启动启动数据库,校验连接参数

Production Readiness

生产就绪性

Data Source Configuration

数据源配置

typescript
// data-source.ts
import { DataSource } from 'typeorm';

export const AppDataSource = new DataSource({
  type: 'postgres',
  url: process.env.DATABASE_URL,
  // SECURITY: Use proper CA certificate in production instead of disabling verification
  // ssl: { rejectUnauthorized: false } is INSECURE - vulnerable to MITM attacks
  ssl: process.env.NODE_ENV === 'production'
    ? { ca: process.env.DB_CA_CERT }
    : false,

  // Connection pool
  extra: {
    max: 20,
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 10000,
  },

  entities: ['dist/entities/**/*.js'],
  migrations: ['dist/migrations/**/*.js'],
  subscribers: ['dist/subscribers/**/*.js'],

  // Never use in production
  synchronize: false,

  // Logging
  logging: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
  logger: 'advanced-console',

  // Cache
  cache: {
    type: 'ioredis',
    options: {
      host: process.env.REDIS_HOST,
      port: parseInt(process.env.REDIS_PORT || '6379'),
    },
    duration: 30000, // 30 seconds
  },
});

// Initialize
AppDataSource.initialize()
  .then(() => console.log('Database connected'))
  .catch((err) => console.error('Database connection error:', err));
typescript
// data-source.ts
import { DataSource } from 'typeorm';

export const AppDataSource = new DataSource({
  type: 'postgres',
  url: process.env.DATABASE_URL,
  // 安全提示:生产环境使用合法 CA 证书,不要禁用证书校验
  // ssl: { rejectUnauthorized: false } 存在中间人攻击风险,是不安全的配置
  ssl: process.env.NODE_ENV === 'production'
    ? { ca: process.env.DB_CA_CERT }
    : false,

  // 连接池
  extra: {
    max: 20,
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 10000,
  },

  entities: ['dist/entities/**/*.js'],
  migrations: ['dist/migrations/**/*.js'],
  subscribers: ['dist/subscribers/**/*.js'],

  // 生产环境绝对不要开启
  synchronize: false,

  // 日志
  logging: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
  logger: 'advanced-console',

  // 缓存
  cache: {
    type: 'ioredis',
    options: {
      host: process.env.REDIS_HOST,
      port: parseInt(process.env.REDIS_PORT || '6379'),
    },
    duration: 30000, // 30 秒
  },
});

// 初始化
AppDataSource.initialize()
  .then(() => console.log('数据库连接成功'))
  .catch((err) => console.error('数据库连接错误:', err));

Transaction Management

事务管理

typescript
import { EntityManager } from 'typeorm';

async function transferFunds(
  manager: EntityManager,
  fromId: number,
  toId: number,
  amount: number
) {
  return await manager.transaction(async (transactionalManager) => {
    const from = await transactionalManager
      .createQueryBuilder(Account, 'account')
      .setLock('pessimistic_write')
      .where('account.id = :id', { id: fromId })
      .getOne();

    if (!from || from.balance < amount) {
      throw new Error('Insufficient funds');
    }

    await transactionalManager
      .createQueryBuilder()
      .update(Account)
      .set({ balance: () => `balance - ${amount}` })
      .where('id = :id', { id: fromId })
      .execute();

    await transactionalManager
      .createQueryBuilder()
      .update(Account)
      .set({ balance: () => `balance + ${amount}` })
      .where('id = :id', { id: toId })
      .execute();
  });
}
typescript
import { EntityManager } from 'typeorm';

async function transferFunds(
  manager: EntityManager,
  fromId: number,
  toId: number,
  amount: number
) {
  return await manager.transaction(async (transactionalManager) => {
    const from = await transactionalManager
      .createQueryBuilder(Account, 'account')
      .setLock('pessimistic_write')
      .where('account.id = :id', { id: fromId })
      .getOne();

    if (!from || from.balance < amount) {
      throw new Error('余额不足');
    }

    await transactionalManager
      .createQueryBuilder()
      .update(Account)
      .set({ balance: () => `balance - ${amount}` })
      .where('id = :id', { id: fromId })
      .execute();

    await transactionalManager
      .createQueryBuilder()
      .update(Account)
      .set({ balance: () => `balance + ${amount}` })
      .where('id = :id', { id: toId })
      .execute();
  });
}

Query Optimization

查询优化

typescript
// Pagination
async function getUsers(page: number, limit: number) {
  const [users, total] = await userRepository.findAndCount({
    skip: (page - 1) * limit,
    take: limit,
    order: { createdAt: 'DESC' },
  });

  return {
    data: users,
    meta: {
      total,
      page,
      lastPage: Math.ceil(total / limit),
    },
  };
}

// Select specific columns
const users = await userRepository
  .createQueryBuilder('user')
  .select(['user.id', 'user.name', 'user.email'])
  .where('user.isActive = :active', { active: true })
  .getMany();

// Batch operations
await userRepository
  .createQueryBuilder()
  .insert()
  .into(User)
  .values(usersToCreate)
  .orIgnore() // Skip duplicates
  .execute();
typescript
// 分页
async function getUsers(page: number, limit: number) {
  const [users, total] = await userRepository.findAndCount({
    skip: (page - 1) * limit,
    take: limit,
    order: { createdAt: 'DESC' },
  });

  return {
    data: users,
    meta: {
      total,
      page,
      lastPage: Math.ceil(total / limit),
    },
  };
}

// 查询指定字段
const users = await userRepository
  .createQueryBuilder('user')
  .select(['user.id', 'user.name', 'user.email'])
  .where('user.isActive = :active', { active: true })
  .getMany();

// 批量操作
await userRepository
  .createQueryBuilder()
  .insert()
  .into(User)
  .values(usersToCreate)
  .orIgnore() // 跳过重复数据
  .execute();

Soft Deletes

软删除

typescript
@Entity()
@DeleteDateColumn()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @DeleteDateColumn()
  deletedAt?: Date;
}

// Soft delete
await userRepository.softDelete(1);

// Include soft deleted
const allUsers = await userRepository
  .createQueryBuilder('user')
  .withDeleted()
  .getMany();

// Restore
await userRepository.restore(1);
typescript
@Entity()
@DeleteDateColumn()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @DeleteDateColumn()
  deletedAt?: Date;
}

// 软删除
await userRepository.softDelete(1);

// 包含软删除数据查询
const allUsers = await userRepository
  .createQueryBuilder('user')
  .withDeleted()
  .getMany();

// 恢复
await userRepository.restore(1);

Migration Best Practices

迁移最佳实践

typescript
// migrations/1234567890-CreateUsersTable.ts
import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateUsersTable1234567890 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE "users" (
        "id" SERIAL PRIMARY KEY,
        "email" VARCHAR(255) UNIQUE NOT NULL,
        "name" VARCHAR(100) NOT NULL,
        "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
      )
    `);
    await queryRunner.query(`
      CREATE INDEX "idx_users_email" ON "users" ("email")
    `);
  }

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

// CLI commands
// npm run typeorm migration:generate -- -n CreateUsersTable
// npm run typeorm migration:run
// npm run typeorm migration:revert
typescript
// migrations/1234567890-CreateUsersTable.ts
import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateUsersTable1234567890 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE "users" (
        "id" SERIAL PRIMARY KEY,
        "email" VARCHAR(255) UNIQUE NOT NULL,
        "name" VARCHAR(100) NOT NULL,
        "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
      )
    `);
    await queryRunner.query(`
      CREATE INDEX "idx_users_email" ON "users" ("email")
    `);
  }

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

// CLI 命令
// npm run typeorm migration:generate -- -n CreateUsersTable
// npm run typeorm migration:run
// npm run typeorm migration:revert

Testing

测试

typescript
// test-utils.ts
import { DataSource } from 'typeorm';

export const TestDataSource = new DataSource({
  type: 'postgres',
  url: process.env.TEST_DATABASE_URL,
  entities: ['src/entities/**/*.ts'],
  synchronize: true,
  dropSchema: true,
});

// tests/user.test.ts
describe('UserRepository', () => {
  beforeAll(async () => {
    await TestDataSource.initialize();
  });

  afterAll(async () => {
    await TestDataSource.destroy();
  });

  beforeEach(async () => {
    await TestDataSource.synchronize(true);
  });

  it('should create user', async () => {
    const repo = TestDataSource.getRepository(User);
    const user = await repo.save({ name: 'Test', email: 'test@example.com' });
    expect(user.id).toBeDefined();
  });
});
typescript
// test-utils.ts
import { DataSource } from 'typeorm';

export const TestDataSource = new DataSource({
  type: 'postgres',
  url: process.env.TEST_DATABASE_URL,
  entities: ['src/entities/**/*.ts'],
  synchronize: true,
  dropSchema: true,
});

// tests/user.test.ts
describe('UserRepository', () => {
  beforeAll(async () => {
    await TestDataSource.initialize();
  });

  afterAll(async () => {
    await TestDataSource.destroy();
  });

  beforeEach(async () => {
    await TestDataSource.synchronize(true);
  });

  it('should create user', async () => {
    const repo = TestDataSource.getRepository(User);
    const user = await repo.save({ name: 'Test', email: 'test@example.com' });
    expect(user.id).toBeDefined();
  });
});

Monitoring Metrics

监控指标

MetricTarget
Query time (p99)< 100ms
Connection pool usage< 80%
Slow query count0
Migration success100%
指标目标值
查询耗时(p99)< 100ms
连接池使用率< 80%
慢查询数量0
迁移成功率100%

Checklist

检查清单

  • Connection pooling configured
  • SSL in production
  • synchronize: false in production
  • Query caching with Redis
  • Transaction management
  • Soft deletes where appropriate
  • Pagination for list queries
  • Migration versioning
  • Logging only errors in production
  • Test database isolation
  • 已配置连接池
  • 生产环境开启 SSL
  • 生产环境关闭 synchronize
  • 配置 Redis 查询缓存
  • 事务管理配置完成
  • 必要场景配置软删除
  • 列表查询配置分页
  • 迁移版本配置完成
  • 生产环境仅输出错误日志
  • 测试数据库隔离配置完成

Reference Documentation

参考文档

  • Relations
  • Migrations
  • 关联关系
  • 迁移