Interceptors
The service-level interceptor option wraps every operation on a service instance. It receives a next callback, the CommandInfo for the current operation, and a reference to the service itself.
Use interceptors for cross-cutting concerns that apply to all operations uniformly — logging, tracing, retries, access control.
Request logging
export class CustomersService extends MongoCollectionService<Customer> {
constructor(db: Db, private readonly logger: Logger) {
super(Customer, {
db,
interceptor: async (next, command, _this) => {
const start = Date.now();
try {
const result = await next();
logger.debug(`${command.method} completed in ${Date.now() - start}ms`);
return result;
} catch (err) {
logger.error(`${command.method} failed`, err);
throw err;
}
},
});
}
}
Read-only enforcement
Reject write operations entirely for a read-only replica:
interceptor: async (next, command) => {
if (command.crud !== 'read') {
throw new ForbiddenError('This service is read-only');
}
return next();
}
Retry on transient errors
interceptor: async (next, command) => {
for (let attempt = 1; attempt <= 3; attempt++) {
try {
return await next();
} catch (err: any) {
if (attempt === 3 || !isTransientError(err)) throw err;
await sleep(50 * attempt);
}
}
}
Interceptors vs lifecycle hooks
| Interceptor | Lifecycle hook | |
|---|---|---|
| Scope | Every operation | Specific operation type |
| Access | CommandInfo, full next() wrapper | Typed command + result |
| Use for | Logging, tracing, retries, access control | Timestamps, validation, audit log, cache bust |
Use hooks when you need typed access to command.input or the operation result. Use the interceptor when you want to wrap the call generically.
Full API reference
→ MongoService — interceptor, Options