Quick Start with Express
This guide walks you through building a minimal OPRA HTTP API using standalone Express — no framework required.
1. Install dependencies
Install the core OPRA packages along with Express.
npm install @opra/common @opra/http express
npm install -D @types/express
2. Define a model
Models are plain TypeScript classes decorated with @ComplexType(). Each field carries its type and validation rules directly.
// src/models/customer.ts
import { ComplexType, ApiField } from '@opra/common';
@ComplexType()
export class Customer {
@ApiField({ type: 'integer' }) declare id: number;
@ApiField({ required: true }) declare name: string;
@ApiField() declare email?: string;
}
3. Define a service (Optional)
Services are plain classes that encapsulate your business logic or data access. Keeping them separate from controllers makes your code easier to test and reuse.
// src/customers/customers.service.ts
import { Customer } from '../models/customer.js';
export class CustomersService {
private readonly db: Customer[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob' },
];
findById(id: number) {
return this.db.find(c => c.id === id);
}
findAll() {
return this.db;
}
}
4. Define a controller
Controllers declare your API operations. Each method is mapped to an HTTP verb and path via @HttpOperation decorators. OPRA validates incoming requests and encodes responses automatically.
// src/customers/customers.controller.ts
import { HttpController, HttpOperation } from '@opra/common';
import { HttpContext } from '@opra/http';
import { Customer } from '../models/customer.js';
import { CustomersService } from './customers.service.js';
@HttpController('/customers')
export class CustomersController {
private readonly service = new CustomersService();
@HttpOperation.GET('/:id', { response: Customer })
async get(ctx: HttpContext) {
return this.service.findById(Number(ctx.pathParams.id));
}
@HttpOperation.GET({ response: [Customer] })
async list(ctx: HttpContext) {
return this.service.findAll();
}
}
5. Bootstrap the application
Create the ApiDocument from your controllers and attach the ExpressAdapter to your Express app. The adapter registers all routes and the $schema endpoint automatically.
// src/main.ts
import express from 'express';
import { ApiDocumentFactory } from '@opra/common';
import { ExpressAdapter } from '@opra/http';
import { CustomersController } from './customers/customers.controller.js';
const app = express();
app.use(express.json());
const document = await ApiDocumentFactory.createDocument({
info: { title: 'My API', version: '1.0' },
api: {
transport: 'http',
controllers: [CustomersController],
},
});
new ExpressAdapter(app, document, { basePath: '/api' });
app.listen(3000, () => {
console.log('Listening on http://localhost:3000');
});
6. Test it
curl http://localhost:3000/api/customers/1
curl http://localhost:3000/api/customers
curl http://localhost:3000/api/$schema
Next steps
- Quick Start with NestJS — same API built with NestJS
- Core Concepts — understand the schema model in depth
- HTTP Adapter — standalone Express adapter reference