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.
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.