Getting Started
1. Define a data type
Services are typed against an OPRA ComplexType. Decorate your class with @OpraSchema.DataType (or register it with the document's node):
import { ComplexType, ApiField } from '@opra/common';
@ComplexType()
export class Customer {
@ApiField() _id: string;
@ApiField() name: string;
@ApiField() email: string;
@ApiField() status: 'active' | 'inactive';
@ApiField() createdAt: Date;
}
2. Create a service class
Extend the service that matches your data shape and pass the data type to the constructor:
import { MongoCollectionService } from '@opra/mongodb';
import { Customer } from '../models/customer.js';
export class CustomersService extends MongoCollectionService<Customer> {
constructor() {
super(Customer);
}
}
The collection name defaults to the data type name ('Customer'). Override it when needed:
super(Customer, { collectionName: 'customers' });
3. Connect a database
Services are stateless about the database connection — you pass db through the options or via for(context). A common pattern is to attach db through an execution context:
import { Db } from 'mongodb';
export class CustomersService extends MongoCollectionService<Customer> {
constructor(readonly db: Db) {
super(Customer, { db });
}
}
4. Use for(context) per request
Every service exposes a for(context, overwriteProperties?) method that returns a scoped copy of the service bound to the current execution context. Call it at the start of every request handler to get a request-scoped instance:
// In an OPRA HTTP controller
async getCustomer(ctx: HttpContext) {
const svc = this.customersService.for(ctx);
return svc.get(ctx.pathParams.id);
}
for() is cheap — it does not create a new class instance, only a shallow proxy that inherits all configuration from the parent.
You can also override any property per-request:
const svc = this.customersService.for(ctx, { scope: 'admin' });
5. NestJS integration
import { Injectable } from '@nestjs/common';
import { InjectDb } from '@opra/nestjs-mongodb'; // or inject Db from your MongoModule
@Injectable()
export class CustomersService extends MongoCollectionService<Customer> {
constructor(@InjectDb() db: Db) {
super(Customer, { db });
}
}
Inject into a controller:
@Injectable()
export class CustomersController {
constructor(private readonly customers: CustomersService) {}
@HttpOperation.Entity.Get({ type: Customer })
async get(ctx: HttpContext) {
return this.customers.for(ctx).get(ctx.pathParams.id);
}
}
Singleton services
For collections that always hold exactly one document, use MongoSingletonService. No _id argument is ever needed:
import { MongoSingletonService } from '@opra/mongodb';
import { AppSettings } from '../models/app-settings.js';
@Injectable()
export class AppSettingsService extends MongoSingletonService<AppSettings> {
constructor(@InjectDb() db: Db) {
super(AppSettings, { db });
}
}
// Usage
const settings = await this.appSettings.for(ctx).get();
await this.appSettings.for(ctx).update({ maintenanceMode: true });
The default singleton _id is 655608925cad472b75fc6485. Override it with { _id: yourId } in the constructor options.
Next steps
- Querying — filter, projection, sort, pagination
- Mutations — create, update, delete patterns
- Lifecycle Hooks — timestamps, validation, audit logging