MongoDB adapter
Installation
- npm
- yarn
- pnpm
npm install @nestjs-cls/transactional-adapter-mongodb
yarn add @nestjs-cls/transactional-adapter-mongodb
pnpm add @nestjs-cls/transactional-adapter-mongodb
Registration
ClsModule.forRoot({
plugins: [
new ClsPluginTransactional({
imports: [
// module in which the MongoClient instance is provided
MongoDBModule,
],
adapter: new TransactionalAdapterMongoDB({
// the injection token of the MongoClient
mongoClientToken: MONGO_CLIENT,
}),
}),
],
});
Typing & usage
To work correctly, the adapter needs to inject an instance of MongoClient
Due to how transactions work in MongoDB, the usage of the MongoDBAdapter adapter is a bit different from the others.
The tx property on the TransactionHost<TransactionalAdapterMongoDB> does not refer to any transactional instance, but rather to a ClientSession instance with an active transaction, or undefined when no transaction is active.
Queries are not executed using the ClientSession instance, but instead the ClientSession instance or undefined is passed to the query as the session option.
The TransactionalAdapterMongoDB does not support the "Transaction Proxy" feature, because proxying an undefined value is not supported by the JavaScript Proxy.
MongoDB does not support savepoints or nested transactions. Using Propagation.Nested will re-use the existing transaction.
Example
@Injectable()
class UserService {
constructor(private readonly userRepository: UserRepository) {}
@Transactional()
async runTransaction() {
// both methods are executed in the same transaction
const user = await this.userRepository.createUser('John');
const foundUser = await this.userRepository.getUserById(user._id);
assert(foundUser._id === user._id);
}
}
@Injectable()
class UserRepository {
constructor(
@Inject(MONGO_CLIENT)
private readonly mongoClient: MongoClient, // use a regular mongoClient here
private readonly txHost: TransactionHost<TransactionalAdapterMongoDB>,
) {}
async getUserById(id: ObjectId) {
// txHost.tx is typed as ClientSession
return this.mongoClient.db('default').collection('user').findOne(
{ _id: id },
{ session: this.txHost.tx }, // here, the `tx` is passed as the `session`
);
}
async createUser(name: string) {
const created = await this.mongo
.db('default')
.collection('user')
.insertOne(
{ name: name, email: `${name}@email.com` },
{ session: this.txHost.tx }, // here, the `tx` is passed as the `session`
);
const createdId = created.insertedId;
const createdUser = await this.getUserById(createdId);
return createdUser;
}
}
Queries don't have to be run using the "raw" MongoClient. You can as well use collection aliases and whatnot. What is important, is that they all reference the same underlying MongoClient instance, and that you pass the tx as the session option to the query.