Getting Started
1. Define a data type
Services are typed against an OPRA ComplexType. Decorate your class with @ComplexType and annotate each field:
import { ComplexType, ApiField } from '@opra/common';
@ComplexType()
export class Product {
@ApiField() _id: string;
@ApiField() name: string;
@ApiField() category: string;
@ApiField() price: number;
@ApiField() inStock: boolean;
}
2. Create a service class
Extend ElasticCollectionService and pass the data type to the constructor:
import { ElasticCollectionService } from '@opra/elastic';
import { Product } from '../models/product.js';
export class ProductsService extends ElasticCollectionService<Product> {
constructor() {
super(Product);
}
}
The index name defaults to the data type name ('Product'). Override it when needed:
super(Product, { indexName: 'products' });
3. Connect a client
Pass an @elastic/elasticsearch Client instance through the constructor options:
import { Client } from '@elastic/elasticsearch';
export class ProductsService extends ElasticCollectionService<Product> {
constructor(readonly client: Client) {
super(Product, { client });
}
}
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:
async getProduct(ctx: HttpContext) {
return this.productsService.for(ctx).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.
5. NestJS integration
import { Injectable } from '@nestjs/common';
import { Client } from '@elastic/elasticsearch';
@Injectable()
export class ProductsService extends ElasticCollectionService<Product> {
constructor(readonly client: Client) {
super(Product, { client });
}
}
Inject into a controller:
@Injectable()
export class ProductsController {
constructor(private readonly products: ProductsService) {}
@HttpOperation.Entity.Get({ type: Product })
async get(ctx: HttpContext) {
return this.products.for(ctx).get(ctx.pathParams.id);
}
}
Next steps
- Querying — filter, projection, sort, pagination
- Mutations — create, update, delete patterns
- Lifecycle Hooks — validation, audit logging