Skip to main content

Client & CLI

OPRA provides a typed HTTP client for consuming your API, and a CLI tool that generates client code automatically from the live API schema. Together they close the loop between server and client — changes on the server surface as TypeScript compile errors on the client.


CLI — generating client code

oprimp connects to a running OPRA service, reads the /$schema endpoint, and generates typed TypeScript models and controller proxies:

npx oprimp https://api.example.com ./src/generated

The generated output contains:

  • Model types — TypeScript interfaces and classes for every data type in the API
  • Controller proxies — Typed wrappers for each HTTP controller so you call methods instead of constructing URLs manually

Re-run oprimp whenever the API changes. If the server renames a field or drops a parameter, the generated types change and any code that relied on the old shape fails at compile time — not at runtime.

# In CI — keep client in sync with the deployed service
npx oprimp https://api.example.com ./src/generated

CLI Reference


HTTP Client

@opra/client is a lightweight HTTP client that works in any JavaScript environment — browser, Node.js, React, Vue, or plain TypeScript. No framework dependencies.

import { OpraHttpClient } from '@opra/client';

const client = new OpraHttpClient('https://api.example.com');

// GET
const orders = await client.get<Order[]>('orders').getBody();

// POST
const created = await client.post<Order>('orders', { productId: 'p1' }).getBody();

// PATCH
await client.patch('orders/123', { status: 'shipped' }).getBody();

// DELETE
await client.delete('orders/123').getBody();

Every request returns an HttpRequestObservable — you can await it like a Promise or pipe it through RxJS operators:

// Promise
const order = await client.get<Order>('orders/123').getBody();

// Observable
userId$.pipe(
switchMap(id => client.get<User>(`users/${id}`))
).subscribe(user => ...);

Errors are normalized into ClientError with a status code and a structured issues array matching the OPRA error format:

import { ClientError } from '@opra/client';

try {
await client.get<Order>('orders/999').getBody();
} catch (err) {
if (err instanceof ClientError) {
console.log(err.status); // 404
console.log(err.issues); // [{ message: 'Not found', code: 'NOT_FOUND' }]
}
}

HTTP Client · Interceptors


Angular

@opra/angular is a drop-in replacement that delegates HTTP transport to Angular's HttpClient, keeping Angular's interceptors, HttpContext, and DI system fully active:

import { OpraAngularClient } from '@opra/angular';

@Injectable({ providedIn: 'root' })
export class ApiService {
constructor(private client: OpraAngularClient) {}

getOrders() {
return this.client.get<Order[]>('orders').getBody();
}
}

Angular Integration


Batch requests & transactions

Send multiple requests in a single HTTP round-trip using client.bundle(). The client serializes each request as a multipart/mixed part, sends them as one POST /$bundle, and distributes the sub-responses back:

const r1 = client.get<Customer>('customers@1');
const r2 = client.get<Order[]>('orders');

const [res1, res2] = await client.bundle([r1, r2]).getResponses();

// Individual requests can still be awaited — resolved from the bundle:
const customer = await r1.getBody();
const orders = await r2.getBody();

To wrap all sub-requests in a single database transaction, use client.transaction() — it appends ?transaction=true automatically. The server commits on full success or rolls back on any failure:

const r1 = client.post('orders', { productId: 'p1', qty: 2 });
const r2 = client.patch('inventory/p1', { reserve: 2 });

// Both commit together — or both roll back.
await client.transaction([r1, r2]).getResponses();

Transaction support is provided out of the box by @opra/mongodb and @opra/sqb — no server-side handler changes needed.

Batch Requests · HttpBundleObservable