I want to create a NestJS decorator @Transactional()
for service methods that will undo all changes made to the database in case of failures.
import { ConflictException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { Brand } from './brand.entity';
import { BRANDS_REPOSITORY } from './brands.repository';
import { USERS_REPOSITORY } from '../users/users.repository';
import { User } from '../users/user.entity';
import { DATA_SOURCE } from '../database/database.providers';
import { Transactional } from '../../decorators/transactional.decorator';
@Injectable()
export class BrandsService {
constructor(
@Inject(BRANDS_REPOSITORY) private brandsRepository: Repository<Brand>,
@Inject(USERS_REPOSITORY) private usersRepository: Repository<User>,
) {}
@Transactional() // <<==
async create(user: User, name: string) { // shouldn't make any changes in the database
let brand = await this.brandsRepository.create({ name });
brand = await this.brandsRepository.save(brand);
user.permittedBrands.push(brand);
await this.usersRepository.save(user);
throw new Error('Something went wrong');
return brand;
}
}
This would be the answer to the question.
import { DataSource } from 'typeorm';
export function Transactional() {
return function (target: any, propertyName: string, descriptor: TypedPropertyDescriptor<any>) {
const method = descriptor.value;
descriptor.value = async function (...args: any[]) {
const dataSource: DataSource = this.dataSource;
const queryRunner = dataSource.createQueryRunner();
await queryRunner.startTransaction();
const createQueryRunner = dataSource.createQueryRunner;
const release = queryRunner.release;
dataSource.createQueryRunner = () => queryRunner;
queryRunner.release = () => Promise.resolve();
try {
const result = await method.apply(this, args);
await queryRunner.commitTransaction();
return result;
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
dataSource.createQueryRunner = createQueryRunner;
queryRunner.release = release;
await queryRunner.release();
}
};
};
}
But I also had to add private dataSource: DataSource
parameter to the constructor of BrandsService, because it is used in the decorator.
import { ConflictException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { Brand } from './brand.entity';
import { BRANDS_REPOSITORY } from './brands.repository';
import { USERS_REPOSITORY } from '../users/users.repository';
import { User } from '../users/user.entity';
import { DATA_SOURCE } from '../database/database.providers';
import { Transactional } from '../../decorators/transactional.decorator';
@Injectable()
export class BrandsService {
constructor(
@Inject(BRANDS_REPOSITORY) private brandsRepository: Repository<Brand>,
@Inject(USERS_REPOSITORY) private usersRepository: Repository<User>,
@Inject(DATA_SOURCE) private dataSource: DataSource, // <<==
) {}
@Transactional()
async create(user: User, name: string) {
let brand = await this.brandsRepository.create({ name });
brand = await this.brandsRepository.save(brand);
user.permittedBrands.push(brand);
await this.usersRepository.save(user);
throw new Error('Something went wrong');
return brand;
}
}