Skip to main content

Mongoose adapter

Installation

npm install @nestjs-cls/transactional-adapter-mongoose

Registration

ClsModule.forRoot({
plugins: [
new ClsPluginTransactional({
imports: [
// module in which the Connection instance is provided
MongooseModule,
],
adapter: new TransactionalAdapterMongoose({
// the injection token of the mongoose Connection
mongooseConnectionToken: Connection,
}),
}),
],
});

Typing & usage

To work correctly, the adapter needs to inject an instance of mongoose Connection.

Due to how transactions work in MongoDB, and in turn in Mongoose, the usage of the Mongoose adapter is a bit different from the others.

The tx property on the TransactionHost<TransactionalAdapterMongoose> does not refer to any transactional instance, but rather to a ClientSession instance of mongodb, with an active transaction, or null when no transaction is active.

Queries are not executed using the ClientSession instance, but instead the ClientSession instance or null is passed to the query as the session option.

important

The TransactionalAdapterMongoose does not support the "Transaction Proxy" feature, because proxying a null value is not supported by the JavaScript Proxy.

Example

database.schemas.ts
const userSchema = new Schema({
name: String,
email: String,
});

const User = mongoose.model('user', userSchema);
user.service.ts
@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);
}
}
user.repository.ts
@Injectable()
class UserRepository {
constructor(
private readonly txHost: TransactionHost<TransactionalAdapterMongoose>,
) {}

async getUserById(id: ObjectId) {
// txHost.tx is typed as ClientSession
return await User.findById(id)
.session(this.txHost.tx);
}

async createUser(name: string) {
const user = new User({ name: name, email: `${name}@email.com` });
await user
.save({ session: this.txHost.tx });
return user;
}
}

Considerations

Using with built-in Mongoose AsyncLocalStorage support

Mongoose > 8.4 has a built-in support for propagating the session via AsyncLocalStorage.

The feature is compatible with @nestjs-cls/transactional and when enabled, one does not have to pass TransactionHost#tx to queries and still enjoy the simplicity of the @Transactional decorator, which starts and ends the underlying transaction automatically.

However, because @nestjs-cls/transactional has no control over the propagation of the session instance via Mongoose's AsyncLocalStorage, there is no implicit support for opting out of an ongoing transaction via TransactionHost#withoutTransaction (or analogously the Propagation.NotSupported mode).

To opt out of an ongoing transaction, you have to explicitly pass null to the session option when calling the query. Alternatively, you can explicitly pass in the value of TransactionHost#tx if the query should support both transactional and non-transactional mode and you want to control it using Propagation.