This is my current code, it supposed to start a transaction in the update method that used other services.
async update(authContext: AuthContext, id: string, updatePortfolioDto: UpdatePortfolioDto, isSaved = false): Promise<Portfolio> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try{
await this.sleeveService.deleteForPortfolio(authContext, id);
const updatedPortfolio = this.portfoliosRepository.update(id, portfolio);
await queryRunner.commitTransaction();
return updatedPortfolio
}catch(err){
await queryRunner.rollbackTransaction();
}finally{
await queryRunner.release();
}
}As you can see, I am calling a method from another sevice and repository.
For example deleteForPortfolio method in the sleeveservice is
export class TypeOrmSleeveRepository implements ISleevesRepository {
private readonly logger = new Logger(TypeOrmSleeveRepository.name);
constructor(@InjectRepository(Sleeve) private readonly sleeveRepository: Repository<Sleeve>) {}
deleteForPortfolio(authContext: AuthContext, portfolioId: string) {
this.sleeveRepository
.createQueryBuilder('sleeve')
.delete()
.where('sleeve.portfolioId = :portfolioId', { portfolioId })
.andWhere('sleeve.tenantId = :tenantId', { tenantId: authContext.tenantId })
.execute();
}
}and the portfolio update method is
async update(id: string, portfolio: Portfolio): Promise<Portfolio> {
try {
const updatedPortfolio = await this.portfolioRepository.preload({
id,
...portfolio
});
if (!updatedPortfolio) {
this.logger.warn(`Portfolio with ID ${id} not found`);
throw new NotFoundException(`Portfolio with ID ${id} not found`);
}
const savedPortfolio = await this.portfolioRepository.save(updatedPortfolio);
this.logger.log(`Updated Portfolio with ID ${id}`);
return savedPortfolio;
} catch (error) {
if (error instanceof NotFoundException) {
throw error;
}
this.logger.error(`Error updating portfolio with ID ${id}: ${error.message}`, error.stack);
throw error;
}
}Currently my code does not work. How can I fix this please ?
how to use transaction across service in nestjs with typeorm - Stack Overflow
Support transaction per request with TypeORM
Why I built typeorm-transactional-decorator
How can I use transactions with typeorm across multiple services
Many solutions are available, they should all be based on SQL transaction management.
Personally I feel that the simplest way to achieve that is to use the same EntityManager instance when you execute code on your database. Then you can use something like:
getConnection().transaction(entityManager -> {
service1.doStuff1(entityManager);
service2.doStuff2(entityManager);
});
You can spawn a QueryRunner from an EntityManager instance that will be wrapped in the same transaction in case you execute raw SQL outside ORM operations. You need also to spawn Repository instances from EntityManager as well or they will execute code outside the main transaction.
typeorm-transactional uses CLS (Continuation Local Storage) to handle and propagate transactions between different repositories and service methods.
@Injectable()
export class PostService {
constructor(
private readonly authorRepository: AuthorRepository,
private readonly postRepository: PostRepository,
) {}
@Transactional() // will open a transaction if one doesn't already exist
async createPost(authorUsername: string, message: string): Promise<Post> {
const author = await this.authorRepository.create({ username: authorUsername });
return this.postRepository.save({ message, author_id: author.id });
}
}
Thanks everyone, I just found the problem is I can't chain the save() right after create() which will call the entity's save() fn instead of the entityManager's one, and start a new transaction.
async createPrice(dto, entityManager){
//wrong code
return entityManager.create(PriceEntity, {...}).save();
//works
const price = entityManager.create(PriceEntity, {...});
return entityManager.save(price);
}
}
try to add @Transactional() decorator before insert function from your services typeorm transaction/ typeorm-transactional-cls-hooked
@Transactional()
public async createProduct(...) {
...
}
I got tired of threading the EntityManager/QueryRunner through every layer of a modular NestJS backend—passing it as a parameter into every service and method. It was noisy, brittle, and honestly, a pain. I went hunting for a decorator-based solution for TypeORM transactions. I found a few, tried them, but they felt overconfigured and, crucially, the same transaction didn’t reliably propagate into deeply nested calls. Maybe I misused them—but the DX wasn’t there.
So I built typeorm-transactional-decorator: a small, focused layer that uses Node’s async hooks to carry a transactional context and patches TypeORM’s DataSource/EntityManager so repositories automatically bind to the current transaction if one exists. You get a dead-simple @Transactional decorator, @IgnoreTransaction to opt out where needed, reliable propagation into nested methods, automatic rollback on errors, and a TransactionResultManager to register side effects for onCommit/onRollback (think: delete an S3 file if the DB transaction fails). It’s based on typeorm-transactional, but simplified and tuned for a predictable, minimal workflow.
It slots neatly into NestJS via TypeOrmModule’s dataSourceFactory, or you can call addTransactionalDataSource(dataSource) in any Node app. The package grows as my needs evolve, but the north star is the same: minimal API, maximum ergonomics for real-world, layered architectures.
How are you handling TypeORM transactions?
NPM: https://www.npmjs.com/package/typeorm-transactional-decorator