Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

285
Views
What is the correct way to work with transactions with NestJs and TypeORM?

I am using NestJS with TypeORM. I have several modules and each module contain service. There are some modules that import other modules and use their services.

// In Person Module
@Injectable()
class UserService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>
  ) {}

  public async deleteUsersByCompany(companyId: string) {
    const usersToDelete = await this.usersRepository
      .createQueryBuilder('user')
      .where('user.companyId = :companyId', { companyId })
      .getMany();

    if (usersToDelete.length > 0) {
      await this.usersRepository.delete(
        usersToDelete.map((user) => user.id),
      );
    }
  }

  ...
}

// Company Module

@Injectable()
class CompanyService {
  constructor(
    @InjectRepository(Company)
    private companysRepository: Repository<Company>,
    private usersService: UserService
  ) {}

  public async deleteCompany(companyId: string) {
    const companyToDelete = await this.companysRepository.find(companyId);

    if (companyToDelete) {
      await usersService.deleteUsersByCompany(companyToDelete.id)
      await this.companysRepository.delete(companyToDelete);
    }
  }

  ...
}

In this example:

  1. I want usersService.deleteUsersByCompany to be atomic operation
  2. I want companyService.deleteCompany to be atomic operation
  3. Both usersService.deleteUsersByCompany and companyService.deleteCompany can be triggered by the user

I want to wrap both actions in transaction. I've read NestJS documentation and TypeOrm documentation but couldn't find an answer how to do this properly.
The main thing here is that usersService.deleteUsersByCompany is being called by companyService.deleteCompany.

  1. How can I support such "nested transactions"?
  2. Does QueryRunner replace the Repository pattern?
  3. Should my services hold both Repository and Connection instances? Repository for non-transactional operations and connection to get QueryRunner?
over 4 years ago · Santiago Trujillo
1 answers
Answer question

0

The easiest way to use transaction is using the EntityManager API, check a simple example from a microservice:

import { WRITE_CONNECTION } from '@my-api/common';
import { Injectable, Logger } from '@nestjs/common';
import { RpcException } from '@nestjs/microservices';
import { InjectEntityManager } from '@nestjs/typeorm';
import { EntityManager } from 'typeorm';

@Injectable()
export class MyService {
  private logger = new Logger(MyService.name);
  constructor(
    @InjectEntityManager(WRITE_CONNECTION) private entityManager: EntityManager,
  ) {}

  async saveSomething(data: string): Promise<void> {
    try {
      return await this.entityManager.transaction(async (entityManager) => {
        const firstRepository = entityManager.getCustomRepository(FirstRepository);
        const secondRepository = entityManager.getCustomRepository(SecondRepository);

        const firstRecord = firstRepository.create({ data });
        await firstRepository.save(firstRecord);

        const secondRecord = secondRepository.create({ data });
        await secondRepository.save(secondRecord);

        // Save entities directly
        await entityManager.save([...]);
      });
    } catch (error) {
      this.logger.error(`Failed saving something, error ${error.message}`, error.stack);
      throw new RpcException(error.message);
    }
  }
}

As you can see, we use the entityManager for read and write operations in the same transaction that can execute a rollback automatically if any of these operations fails.

For more details check the repository of TypeORM with examples here.

So if you want to wrap both actions in the same transaction, you need to call a method and pass the repository as a parameter. e.g:

async findOneItem(
  itemId: number,
  myRepository: MyRepository = this.myRepository, // Optional parameter if needed
) {
  return await myRepository.findOne(itemId);
}
over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!