TypeORM Core Knowledge
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: typeorm for comprehensive documentation.
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
Entity Definition
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
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);
Query Builder
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
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 |
synchronize: true in production |
Can drop tables, loses data |
Always use migrations in production |
Using find() without relations |
N+1 query problem |
Use relations option or QueryBuilder with joins |
| Not using transactions for multi-step ops |
Data inconsistency risk |
Wrap related operations in transaction() |
| Hardcoded credentials in DataSource |
Security vulnerability |
Use environment variables |
| No connection pool configuration |
Connection exhaustion |
Set extra.max and pool timeouts |
Using @Column() without type for large text |
May truncate data |
Specify type: 'text' for long content |
| 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 @Index() decorators |
Quick Troubleshooting
| Issue |
Likely Cause |
Solution |
| "Cannot find name 'AppDataSource'" |
DataSource not initialized |
Call AppDataSource.initialize() at startup |
| "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 emitDecoratorMetadata and experimentalDecorators in tsconfig |
| "Repository not found" |
Entity not registered |
Add entity to DataSource entities array |
| Slow queries |
Missing indexes, no joins |
Add indexes, use leftJoinAndSelect |
| Connection pool exhausted |
Too many concurrent queries |
Increase extra.max pool size |
| "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 |
Production Readiness
Data Source Configuration
// 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));
Transaction Management
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();
});
}
Query Optimization
// 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();
Soft Deletes
@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);
Migration Best Practices
// 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
Testing
// 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% |
Checklist
Reference Documentation