@Injectable()
export class TransfersService {
constructor(private dataSource: DataSource) {}
transfer(fromId: number, toId: number, amount: number) {
return this.dataSource.transaction(async (manager) => {
await manager.decrement(Account, { id: fromId }, "balance", amount);
await manager.increment(Account, { id: toId }, "balance", amount);
});
}
}
Inject the DataSource and call dataSource.transaction(async (manager) => ...). This works once TypeOrmModule is set up.
The transaction method opens a transaction. It commits when the callback finishes successfully. It rolls back if the callback throws.
Every query inside must use the manager that the callback receives. Don't use an injected repository there. Injected repositories run outside the transaction, so their changes are not rolled back.
For more control, use a QueryRunner:
- Create it with
dataSource.createQueryRunner(). - Call
connect()andstartTransaction(). - In a try/catch, call
commitTransaction()on success orrollbackTransaction()on error. - Always call
release()infinally, so the connection goes back to the pool.
The Nest docs suggest putting this logic in its own class. Then the transaction logic is easy to mock in unit tests.
- Inject the
DataSource. dataSource.transaction(cb)commits on success and rolls back on error.- Use the callback's
manager, not injected repositories. - Use a
QueryRunnerfor manual control, and always callrelease().
Read more
- Database (https://docs.nestjs.com/techniques/database)docs.nestjs.com
- TypeORM (https://typeorm.io/)typeorm.io