Skip to main content

Transactions

Use withTransaction when you need to execute multiple service operations atomically. It is an instance method called from a controller endpoint — the execution context comes from the request.

The callback receives the session and a transaction-scoped copy of the service as its second argument. Use that instance directly inside the callback. For other services, pass { session } explicitly to for().

@(HttpOperation({ method: 'POST', path: 'transfer-balance' })
.QueryParam('fromId', String)
.QueryParam('toId', String)
.QueryParam('amount', Number))
async transferBalance(ctx: HttpContext) {
const { fromId, toId, amount } = ctx.queryParams;
await this.accountsService.for(ctx).withTransaction(async (session, accountsSvc) => {
await accountsSvc.updateOnly(fromId, {
balance: { $inc: -amount } as any,
});
await accountsSvc.updateOnly(toId, {
balance: { $inc: amount } as any,
});
});
}

Multi-collection transactions

When multiple services are involved, pass session explicitly to each for() call so they all join the same transaction:

async placeOrder(ctx: HttpContext) {
const orderInput = await ctx.request.readBody<OrderInputDTO>();
await this.ordersService.for(ctx).withTransaction(async (session, ordersSvc) => {
const order = await ordersSvc.create(orderInput);

await this.inventoryService.for(ctx, { session }).updateMany(
{ quantity: { $inc: -1 } },
{ filter: { productId: order.productId } },
);

await this.customerService.for(ctx, { session }).updateOnly(order.customerId, {
_$push: { orderIds: order._id },
});
});
}

Transactions inside a service

Expose a dedicated method on the service when you need a transaction that coordinates multiple operations internally. Inside service methods you don't need to pass ctxthis already carries the context set by the caller via for(ctx). Pass this to other services' for() so they inherit the same context:

export class OrdersService extends MongoCollectionService<Order> {
constructor(
db: Db,
private readonly inventory: InventoryService,
) {
super(Order, { db });
}

async createWithInventoryCheck(input: PartialDTO<Order>) {
return this.withTransaction(async (session, self) => {
const order = await self.create(input);
await this.inventory.for(this, { session }).updateOnly(
input.productId,
{ $inc: { quantity: -input.quantity } } as any,
);
return order;
});
}
}

Full API reference

MongoServicewithTransaction