Skip to content

Entity_manager

Chapter 15: Entity Manager & Repository Differences

Section titled “Chapter 15: Entity Manager & Repository Differences”

Understanding Entity Manager vs Repository

Section titled “Understanding Entity Manager vs Repository”

Both Entity Manager and Repository provide data access, but they differ in scope and usage.

Entity Manager vs Repository
================================================================================
Entity Manager Repository
+---------------------------+ +---------------------------+
| | | |
| Central point for ALL | | Dedicated to ONE |
| entities | | entity |
| | | |
| +---------------------+ | | +---------------------+ |
| | find(User) | | | | find() | |
| | find(Post) | | | | findOne() | |
| | find(Comment) | | | | save() | |
| | ... | | | | remove() | |
| +---------------------+ | | +---------------------+ |
| | | |
| Must specify entity type | | Entity type implicit |
| | | |
+---------------------------+ +---------------------------+
================================================================================

import { AppDataSource } from './data-source';
// From DataSource
const entityManager = AppDataSource.manager;
// From Repository
const entityManager = userRepository.manager;
// Create new QueryRunner
const queryRunner = AppDataSource.createQueryRunner();
const entityManager = queryRunner.manager;
import { EntityManager } from 'typeorm';
import { User } from './entities/user.entity';
import { Post } from './entities/post.entity';
async function entityManagerExamples(manager: EntityManager) {
// Find
const users = await manager.find(User);
const posts = await manager.find(Post);
// Find with conditions
const activeUsers = await manager.find(User, {
where: { isActive: true },
});
// Find one
const user = await manager.findOne(User, {
where: { id: 1 },
});
// Save
const newUser = manager.create(User, {
name: 'John',
email: 'john@example.com',
});
await manager.save(newUser);
// Remove
await manager.remove(user);
// Delete
await manager.delete(User, { id: 1 });
// Update
await manager.update(User, { id: 1 }, { name: 'Updated' });
// Count
const count = await manager.count(User);
// Raw query
const result = await manager.query('SELECT * FROM users');
// Query builder
const qb = manager.createQueryBuilder(User, 'user');
// Transaction
await manager.transaction(async (tm) => {
// tm is another EntityManager
await tm.save(User, { name: 'User 1' });
await tm.save(Post, { title: 'Post 1' });
});
}

import { AppDataSource } from './data-source';
import { User } from './entities/user.entity';
// From DataSource
const userRepository = AppDataSource.getRepository(User);
// From Entity Manager
const userRepository = AppDataSource.manager.getRepository(User);
// In NestJS
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
}
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
async function repositoryExamples(repository: Repository<User>) {
// Find (entity type implicit)
const users = await repository.find();
// Find with conditions
const activeUsers = await repository.find({
where: { isActive: true },
});
// Find one
const user = await repository.findOne({
where: { id: 1 },
});
// Create
const newUser = repository.create({
name: 'John',
email: 'john@example.com',
});
await repository.save(newUser);
// Remove
await repository.remove(user);
// Delete
await repository.delete({ id: 1 });
// Update
await repository.update({ id: 1 }, { name: 'Updated' });
// Count
const count = await repository.count();
// Query builder
const qb = repository.createQueryBuilder('user');
}

Entity Manager vs Repository Comparison
+------------------------------------------------------------------+
| |
| Aspect | Entity Manager | Repository |
| -------------------|-------------------|----------------------|
| Scope | All entities | Single entity |
| Type specification | Required | Implicit |
| Best for | Cross-entity ops | Single entity ops |
| Transactions | Built-in | Via manager |
| Custom methods | Not possible | Extendable |
| Dependency Inj. | Less common | Common pattern |
| Type safety | Less strict | More strict |
| |
+------------------------------------------------------------------+
// 1. Cross-entity operations
async function transferData(manager: EntityManager) {
const users = await manager.find(User);
const posts = await manager.find(Post);
// Work with both entities
}
// 2. Transactions with multiple entities
async function createUserWithPosts(manager: EntityManager) {
await manager.transaction(async (tm) => {
const user = tm.create(User, { name: 'John' });
await tm.save(user);
const post = tm.create(Post, { title: 'First Post', author: user });
await tm.save(post);
});
}
// 3. Raw SQL queries
async function executeRawQuery(manager: EntityManager) {
const result = await manager.query(`
SELECT u.*, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.author_id
GROUP BY u.id
`);
return result;
}
// 4. Dynamic entity operations
async function dynamicOperation(
manager: EntityManager,
entityName: string,
) {
const entity = getEntityByName(entityName);
return manager.find(entity);
}
// 1. Single entity operations
async function getUsers(repository: Repository<User>) {
return repository.find();
}
// 2. Custom query methods
export class UserRepository extends Repository<User> {
async findActive(): Promise<User[]> {
return this.find({ where: { isActive: true } });
}
}
// 3. Dependency injection pattern
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
}
// 4. Type-safe operations
async function findUser(id: number, repository: Repository<User>) {
return repository.findOne({ where: { id } });
// Return type is User | null
}

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Repository, DataSource, EntityManager } from 'typeorm';
import { User } from './user.entity';
import { Post } from './post.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
@InjectRepository(Post)
private postRepository: Repository<Post>,
@InjectDataSource()
private dataSource: DataSource,
) {}
// Using repositories
async findUser(id: number) {
return this.userRepository.findOne({ where: { id } });
}
// Using entity manager for cross-entity
async getUserWithStats(id: number) {
return this.dataSource.manager
.createQueryBuilder(User, 'user')
.leftJoin('user.posts', 'post')
.select(['user.id', 'user.name'])
.addSelect('COUNT(post.id)', 'postCount')
.where('user.id = :id', { id })
.groupBy('user.id')
.getRawOne();
}
// Transaction with both
async createUserWithPosts(userData: any, postsData: any[]) {
return this.dataSource.transaction(async (manager) => {
// Use manager for transaction
const user = manager.create(User, userData);
await manager.save(user);
// Or use repository's manager
const posts = this.postRepository.create(
postsData.map(p => ({ ...p, author: user }))
);
await manager.save(posts);
return user;
});
}
}

// Method 1: transaction() with callback
await entityManager.transaction(async (manager) => {
await manager.save(User, { name: 'User 1' });
await manager.save(Post, { title: 'Post 1' });
});
// Method 2: Manual transaction control
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
await queryRunner.manager.save(User, { name: 'User 1' });
await queryRunner.manager.save(Post, { title: 'Post 1' });
await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
// Repository uses its manager for transactions
await userRepository.manager.transaction(async (manager) => {
const user = manager.create(User, { name: 'User 1' });
await manager.save(user);
const post = manager.create(Post, { title: 'Post 1', author: user });
await manager.save(post);
});

Performance Comparison
+------------------------------------------------------------------+
| |
| Operation | Entity Manager | Repository |
| -------------------|----------------|------------------------|
| Single entity find | Similar | Similar |
| Multi-entity ops | Better | Multiple repos needed |
| Transactions | Native | Via manager |
| Type inference | Slower | Faster |
| Memory usage | Higher | Lower |
| |
+------------------------------------------------------------------+

Best Practices
+------------------------------------------------------------------+
| |
| 1. Use Repository for single entity operations |
| - Cleaner code |
| - Better type safety |
| - Dependency injection friendly |
| |
| 2. Use Entity Manager for cross-entity operations |
| - Single transaction scope |
| - Consistent data access |
| |
| 3. Use Entity Manager for transactions |
| - Atomic operations |
| - Automatic rollback |
| |
| 4. Extend Repository for custom methods |
| - Encapsulate complex queries |
| - Reusable logic |
| |
| 5. Don't mix both in same operation |
| - Choose one approach |
| - Be consistent |
| |
+------------------------------------------------------------------+

Quick Reference
+------------------------------------------------------------------+
| |
| Use Entity Manager when: |
| - Working with multiple entities |
| - Need transactions |
| - Executing raw SQL |
| - Dynamic entity operations |
| |
| Use Repository when: |
| - Working with single entity |
| - Need custom query methods |
| - Using dependency injection |
| - Want cleaner, type-safe code |
| |
+------------------------------------------------------------------+

Chapter 16: Query Builder Fundamentals


Last Updated: February 2026