Creating REST API with Jumentix
Responsibility in context
- Owns: HTTP adapter + application use-cases for REST
- Used with: sdk-rest-client, persistence packages, backend-template app hub
- Not responsible for: WebSocket/gRPC realtime (see realtime guide) or offline IndexedDB (Cana)
What it is
This guide walks you through bootstrapping, running, and validating a REST service using the Jumentix backend template. You will wire domain logic to an HTTP adapter (Express by default) and call the API from a typed client.
Why it exists
Most junior teams start by editing controller files and hope routes stay aligned with documentation. Jumentix inverts that: OpenAPI is the contract, domain code sits in the center, and the HTTP framework is a replaceable adapter. This guide gives you a repeatable path from env profile → running server → green tests.
Prerequisites
| Item | Required | Notes |
|---|---|---|
| Bun 1.3.13+ | Yes | See Getting started |
| Completed getting-started | Recommended | Mental model + Cana playground |
| Backend template scaffold | Yes | Use the monorepo apps/backend-template or your internal bootstrap flow |
| Service Management (optional) | For design-first teams | Domain Designer generates entities aligned with OpenAPI |
Environment file: apps/backend-template/src/config/.env.dev (or your scaffold’s equivalent).
Glossary
| Term | Meaning on this page |
|---|---|
| REST | HTTP API using verbs (GET, POST, …) and JSON payloads. |
| OpenAPI | Spec at spec/1.0.0.yml describing paths, schemas, and operationIds. |
| Runtime profile | Env vars (JUMENTIX_HTTP_FRAMEWORK, JUMENTIX_REALTIME_API) selecting adapters. |
| Controller | Interface-layer class mapping HTTP requests to use-case calls. |
| Use case | Application service implementing one business operation. |
| Repository port | Interface for persistence; implemented by a database adapter. |
| HTTP adapter | Express/Fastify/etc. module mounting routes from OpenAPI operationIds. |
| oas:check-routes | Script verifying every OpenAPI path resolves to a registered handler. |
Numbered steps
Step 1 — Select the REST runtime profile (< 5 minutes)
- Open your env file (default:
apps/backend-template/src/config/.env.dev). - Set REST-only mode:
JUMENTIX_HTTP_FRAMEWORK=express
JUMENTIX_REALTIME_API=noSupported HTTP frameworks are listed on HTTP adapters.
- Start the dev server from the monorepo root:
bun run dev:httpOr from inside apps/backend-template:
bun run dev:restSuccess check: PM2 reports jumentix-dev-http online; curl or browser hits
/health (or your scaffold’s health route) with HTTP 200.
Step 2 — Model domain and contracts (< 15 minutes)
- Design-first (recommended): open Service Management → Domain Designer. Define entities, relationships, and bounded contexts.
- Spec-first: edit OpenAPI at
spec/1.0.0.yml— add paths, schemas, and stableoperationIdvalues. - Keep request/response DTOs aligned with controller method inputs/outputs.
- Shared contract packages (
@jumentix/shared-contracts) should mirror the same shapes; see /docs/jumentix/packages/shared-contracts.
Success check: bun run oas:check-routes passes with zero unresolved routes.
Step 3 — Implement the domain flow (< 30 minutes per feature)
Follow the backend template layering — bottom to top:
- Domain — entities, value objects, domain events under
src/domain/. - Ports — repository and gateway interfaces your use cases depend on.
- Use cases — application services orchestrating domain rules.
- Controllers — translate HTTP DTOs ↔ use-case inputs/outputs.
- Handlers — framework-specific route bindings (Express router modules).
Rule: domain and use cases must not import Express/Fastify. Only adapters do.
Success check: unit tests for the use case pass without starting HTTP.
Step 4 — Bind to the HTTP adapter (< 10 minutes)
- Confirm
JUMENTIX_HTTP_FRAMEWORKmatches the adapter you wired. - REST startup loads
start-rest-api.ts(or your scaffold entry) which registers handlers from module interfaces. - Each OpenAPI
operationIdmaps to one controller method via the adapter registry.
Swap adapters by changing env only — domain code stays untouched:
JUMENTIX_HTTP_FRAMEWORK=fastify # example; confirm support on adapters pageSuccess check: manual request to a new endpoint returns the contract-shaped JSON.
Step 5 — Validate quality gates (< 10 minutes)
Run from monorepo root (or bun --cwd ../.. from backend-template):
bun run lint
bun run test:unit
bun run oas:check-routes
bun run test:integration:expressOptional smoke before pushing:
cd apps/backend-template && bun run test:integration:smokeSuccess check: all four commands exit 0; integration test hits real HTTP against the running profile.
Step 6 — Call the API from a client (junior path)
In Node or the browser docs playground, inject the OpenAPI document instead of
loading it from disk with fs:
const client = api.createMockClient();
const result = await client.request({ method: 'GET', path: '/health' });
console.log(result.status, result.body);For production clients, use @jumentix/sdk-rest-client with the same injected spec
in browser bundles.
Examples
Static mock client
const client = api.createMockClient();
const result = await client.request({ method: 'GET', path: '/health' });
console.log(result.status, result.body);Interactive REST client playground
Try the mocked REST client — Run should return a successful /health response:
REST client with mock fetch
Call Task OpenAPI operations with a browser-safe mock client.
### REST client with mock fetch
```ts
const client = api.createMockClient();
const created = await client.request({
operationId: 'createTask',
method: 'POST',
path: '/tasks',
body: {
id: 'task-1',
title: 'Generate REST SDK example',
categoryId: 'work',
completed: false
}
});
const listed = await client.request({
operationId: 'listTasks',
method: 'GET',
path: '/tasks?categoryId=work'
});
return {
created,
listed
};
```const client = api.createMockClient();
const created = await client.request({
operationId: 'createTask',
method: 'POST',
path: '/tasks',
body: {
id: 'task-1',
title: 'Generate REST SDK example',
categoryId: 'work',
completed: false
}
});
const listed = await client.request({
operationId: 'listTasks',
method: 'GET',
path: '/tasks?categoryId=work'
});
return {
created,
listed
};Common errors
| Symptom | Likely cause | Fix | Verify success |
|---|---|---|---|
| PM2 process exits immediately | Invalid env file path or missing secret | Confirm --env-file=./apps/backend-template/src/config/.env.dev | bun run dev:http stays online |
oas:check-routes fails | OpenAPI path has no handler | Add handler + controller method; rerun check | Script exits 0 |
| 404 on documented route | Wrong HTTP framework adapter loaded | Match JUMENTIX_HTTP_FRAMEWORK to wired module | curl returns 200 |
| Integration test timeout | Server not running or wrong port | Start dev profile before integration suite | test:integration:express green |
| Browser client cannot load YAML | fs.readFile in bundle | Inject parsed OpenAPI object (playground pattern) | Mock client works in browser |
| Domain imports Express | Layer violation | Move HTTP code to adapter/handler layer | arch:check-boundaries passes |
Junior checklist (“I can …”)
- Set
JUMENTIX_HTTP_FRAMEWORKandJUMENTIX_REALTIME_API=noand startbun run dev:http. - Locate
spec/1.0.0.ymland explain what anoperationIdis. - Add or trace one path: OpenAPI → controller → use case → repository port.
- Run
bun run oas:check-routesandbun run test:integration:expresssuccessfully. - Call
/healthvia the sdk-rest-client playground (Run green). - Name one HTTP adapter alternative without changing domain code.
Next step
When you need push notifications or live updates, continue to Create a Realtime API. For adapter reference, see HTTP adapters and Errors and responses.