typeorm
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTypeORM Core Knowledge
TypeORM 核心知识
Deep Knowledge: Usewith technology:mcp__documentation__fetch_docsfor comprehensive documentation.typeorm
深度知识:调用并指定技术为mcp__documentation__fetch_docs可获取完整文档。typeorm
When NOT to Use This Skill
不适用本技能的场景
- Prisma Projects: Use skill for Prisma-based applications
prisma - Drizzle Projects: Use skill for Drizzle ORM
drizzle - Python Applications: Use skill for Python ORMs
sqlalchemy - Raw SQL Operations: Use MCP server for direct SQL
database-query - NoSQL Databases: Use skill for MongoDB (TypeORM MongoDB support is limited)
mongodb - Database Design: Consult or
sql-expertfor schema architecturearchitect-expert - Migration Strategy: Engage for production deployment planning
devops-expert
- Prisma 项目:Prisma 技术栈应用请使用 技能
prisma - Drizzle 项目:Drizzle ORM 相关请使用 技能
drizzle - Python 应用:Python 技术栈 ORM 请使用 技能
sqlalchemy - 原生 SQL 操作:直接执行 SQL 请使用 MCP 服务
database-query - NoSQL 数据库:MongoDB 相关请使用 技能(TypeORM 对 MongoDB 支持有限)
mongodb - 数据库设计:库表架构设计请咨询 或
sql-expertarchitect-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-Pattern | Why It's Bad | Better Approach |
|---|---|---|
| Can drop tables, loses data | Always use migrations in production |
Using | N+1 query problem | Use |
| Not using transactions for multi-step ops | Data inconsistency risk | Wrap related operations in |
| Hardcoded credentials in DataSource | Security vulnerability | Use environment variables |
| No connection pool configuration | Connection exhaustion | Set |
Using | May truncate data | Specify |
| Lazy loading relations everywhere | Performance issues, N+1 queries | Use eager loading strategically |
| Not handling unique constraint errors | Poor error messages to users | Catch and handle constraint violations |
| Manual SQL without parameters | SQL injection risk | Use QueryBuilder with parameters |
| No indexes on frequently queried columns | Slow queries | Add |
| 反模式 | 存在的问题 | 更优方案 |
|---|---|---|
生产环境开启 | 可能删除表导致数据丢失 | 生产环境始终使用迁移功能 |
使用 | 导致 N+1 查询问题 | 使用 |
| 多步操作不使用事务 | 存在数据不一致风险 | 将关联操作包裹在 |
| 数据源配置中硬编码凭证 | 存在安全漏洞 | 使用环境变量存储敏感信息 |
| 未配置连接池 | 可能出现连接耗尽问题 | 设置 |
大文本字段使用 | 可能出现数据截断 | 长内容指定 |
| 所有关联关系都使用懒加载 | 性能问题,N+1 查询 | 策略性使用预加载 |
| 不处理唯一约束错误 | 用户侧错误提示不友好 | 捕获并处理约束违反异常 |
| 手动写 SQL 不使用参数 | 存在 SQL 注入风险 | 使用带参数的 Query Builder |
| 高频查询字段未加索引 | 查询速度慢 | 添加 |
Quick Troubleshooting
快速排障
| Issue | Likely Cause | Solution |
|---|---|---|
| "Cannot find name 'AppDataSource'" | DataSource not initialized | Call |
| "relation does not exist" | Migration not run | Execute pending migrations |
| "column does not exist" | Entity/DB out of sync | Generate and run new migration |
| Type errors on entities | Decorator metadata issue | Enable |
| "Repository not found" | Entity not registered | Add entity to DataSource |
| Slow queries | Missing indexes, no joins | Add indexes, use |
| Connection pool exhausted | Too many concurrent queries | Increase |
| "Cannot query across many-to-many" | Missing join table | Add explicit join table or use QueryBuilder |
| Migration generation creates no file | No entity changes detected | Manually create migration if needed |
| "ECONNREFUSED" | Database not running | Start database, verify connection details |
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| "Cannot find name 'AppDataSource'" | 数据源未初始化 | 应用启动时调用 |
| "relation does not exist" | 迁移未执行 | 执行待处理的迁移 |
| "column does not exist" | 实体和数据库结构不同步 | 生成并执行新的迁移 |
| 实体类型错误 | 装饰器元数据配置问题 | 在 tsconfig 中开启 |
| "Repository not found" | 实体未注册 | 将实体添加到 DataSource 的 |
| 查询慢 | 缺少索引、未使用关联查询 | 添加索引,使用 |
| 连接池耗尽 | 并发查询过多 | 调大 |
| "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:reverttypescript
// 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:revertTesting
测试
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
监控指标
| Metric | Target |
|---|---|
| Query time (p99) | < 100ms |
| Connection pool usage | < 80% |
| Slow query count | 0 |
| Migration success | 100% |
| 指标 | 目标值 |
|---|---|
| 查询耗时(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
- 关联关系
- 迁移