Skip to main content

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: '[email protected]' },
{ 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