Skip to main content

oprimp CLI

Syntax

oprimp <serviceUrl> <outDir> [options]

Arguments

ArgumentDescription
serviceUrlBase URL of the running OPRA service. The generator fetches the schema from <serviceUrl>/$schema.
outDirDirectory where generated files are written. Created if it does not exist. Existing contents are deleted before generation.

Options

OptionDescription
--extAppend .js extension to all import paths in generated files. Required when targeting ESM output.
--refnsExport referenced API documents under separate namespaces. Useful when your service references external OPRA documents.
--no-colorDisable colored output in log messages.
-V, --versionPrint the installed @opra/cli version and exit.
-h, --helpPrint usage information and exit.

Output structure

The output directory has the following structure:

src/generated/
├── index.ts # Barrel — re-exports everything
├── CustomerApi.ts # Root API class
├── http-controller-node.ts # Base class for all controllers
├── api/ # Controller proxies
│ ├── CustomersController.ts
│ ├── CustomerController.ts
│ └── CustomerController/
│ └── CustomerNotesController.ts
├── models/ # Data types defined in this service
│ └── types/
│ └── AvatarMetadata.ts
└── references/ # Types from referenced external documents
└── CustomerModelsDocument/
└── models/
├── enums/
│ └── Gender.ts
└── types/
├── Customer.ts
├── Address.ts
└── ...

Embedded documentation

All API documentation from the server schema is carried into the generated code as JSDoc comments. Data types include field descriptions, constraints, and schema URLs — so your IDE shows the documentation inline as you type:

/**
* Customer information
* @url http://api.example.com/$schema/#types/Customer
*/
export interface Customer extends Record, Person {
uid?: string;
active?: boolean;
/** @default 1 */
rate?: number;
/** @exclusive */
address?: Address;
/** @exclusive @readonly */
readonly country?: Country;
}

Enums include descriptions for every member:

/**
* The gender of a person
* @url http://api.example.com/$schema/#types/Gender
*/
export enum Gender {
/** Male */ MALE = 'M',
/** Female */ FEMALE = 'F',
/** Other */ OTHER = 'O',
/** Unknown */ UNKNOWN = 'U',
}

Controller methods are documented with parameter descriptions and the corresponding API URL:

/**
* findMany operation
* @param $params.limit - Determines number of returning instances
* @param $params.filter - Determines filter fields
* @param $params.sort - Determines sort fields
* @param $params.projection - Determines fields projection
* @apiUrl http://api.example.com/Customers
*/
findMany($params?: {
limit?: number;
skip?: number;
count?: boolean;
sort?: ('_id' | '-_id' | 'givenName' | '-givenName' | 'familyName' | '-familyName')[];
filter?: object;
}): HttpRequestObservable<OperationResult<PartialDTO<Customer>[]>>

Using the generated code

The root API class is named after your service. All controllers are accessible as $-prefixed properties on it. Pass an OpraHttpClient instance to the constructor:

import { OpraHttpClient } from '@opra/client';
import { CustomerApi } from './generated/CustomerApi';

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

Call controller methods directly. Every method returns an HttpRequestObservable — use .getBody() to resolve it as a Promise:

// List
const result = await api.$customers.findMany().getBody();

// Get by id
const customer = await api.$customer.get(42).getBody();

// Create
const created = await api.$customers.create({ givenName: 'Jane', familyName: 'Doe' }).getBody();

// Update
await api.$customer.update(42, { familyName: 'Smith' }).getBody();

// Delete
await api.$customer.delete(42).getBody();

Chain .param() and .header() to add query parameters or custom headers:

// Filter, sort, pagination
const result = await api.$customers
.findMany({ limit: 20, skip: 0, sort: ['familyName', '-givenName'] })
.param('filter', 'address.countryCode = "US"')
.getBody();

// Custom headers
const customer = await api.$customer
.get(42)
.header('X-Tenant-Id', tenantId)
.header('X-Correlation-Id', crypto.randomUUID())
.getBody();

Nested controllers are accessible as properties on their parent controller:

const notes = await api.$customer.$customerNotes
.findMany({ limit: 10 })
.param('filter', 'rank > 3')
.getBody();

package.json script

Add a codegen script so any team member can refresh the client with a single command:

{
"scripts": {
"codegen": "oprimp https://api.example.com ./src/generated"
}
}

For ESM projects, add --ext:

{
"scripts": {
"codegen": "oprimp https://api.example.com ./src/generated --ext"
}
}

CI/CD integration

Run oprimp as part of your build pipeline to ensure the generated client is always in sync with the deployed service.

- name: Generate API client
run: npx oprimp ${{ secrets.API_URL }} ./src/generated --no-color

- name: Build
run: npm run build

Run the codegen step after the service is deployed and before the frontend build.


Programmatic usage

Use TsGenerator directly when you need more control — custom logging, in-memory output, or integration into an existing build script:

import { TsGenerator } from '@opra/cli';

await new TsGenerator({
serviceUrl: process.env.API_URL!,
outDir: './src/generated',
importExt: true,
fileHeader: `/* Generated ${new Date().toISOString()} */`,
logger: console,
}).generate();

See the TsGenerator reference for all options.


Exit codes

CodeMeaning
0Generation completed successfully
1Generation failed (schema fetch error, write error, etc.)