Skip to main content

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