API-First Development
Axiom follows a contract-first API design. The OpenAPI specification is the source of truth — JAX-RS interfaces are generated from it at build time, and backend resource classes implement those generated interfaces.
The OpenAPI Contract
The API specification lives at:
All REST endpoints, request/response schemas, and path parameters are defined here. This file is the starting point for any API change.
Code Generation
The common/api module uses the
Apitomy Codegen Maven plugin to generate
JAX-RS interfaces from the OpenAPI spec at compile time.
Generated output goes to:
The generated code includes:
- Resource interfaces — one per API tag/group, with JAX-RS annotations
(
@Path,@GET,@POST, etc.) - Model classes — POJOs with Jackson
@JsonPropertyannotations for request/response bodies
The plugin is configured in common/api/pom.xml:
<plugin>
<groupId>io.apitomy</groupId>
<artifactId>apitomy-codegen-maven-plugin</artifactId>
<configuration>
<javaPackage>io.apitomy.axiom.api</javaPackage>
</configuration>
</plugin>
Implementing Endpoints
Backend implementations live in:
Each resource class implements the corresponding generated interface:
@ApplicationScoped
public class ProjectsResourceImpl implements ProjectsResource {
@Override
public Response getProjects(/* params */) {
// implementation
}
}
The generated interface provides the JAX-RS annotations — the implementation class only needs to provide the business logic.
Adding a New Endpoint
Step 1: Edit the OpenAPI Spec
Add the path, operation, and any new schemas to common/api/src/main/resources/openapi.json.
Step 2: Regenerate Interfaces
This regenerates the JAX-RS interfaces. Check
target/generated-sources/jaxrs/io/apitomy/axiom/api/ to see the new interface
method.
Step 3: Implement the Endpoint
In app/src/main/java/io/apitomy/axiom/app/rest/, either update an existing
*ResourceImpl.java or create a new one implementing the generated interface.
Step 4: Add UI API Functions
In ui/src/config/api.ts, add:
- TypeScript interfaces matching the request/response schemas
- A
fetch()function calling the new endpoint
All API functions follow the same pattern:
const API_BASE = `${getApiBaseUrl()}/api/v1`;
export async function fetchWidgets(): Promise<Widget[]> {
const resp = await fetch(`${API_BASE}/widgets`);
if (!resp.ok) throw new Error("Failed to fetch widgets");
return resp.json();
}
API Base URL
The API base URL is resolved differently in development and production:
| Mode | Base URL | Mechanism |
|---|---|---|
| Development | (empty string) | Vite proxy forwards /api to localhost:9090 |
| Production | From window.AXIOM_API_URL |
Injected by the backend at runtime |
This is handled by getApiBaseUrl() in api.ts.
SPA Routing
SpaRoutingFilter.java in the app module catches all non-API routes and forwards
them to index.html, allowing React Router to handle client-side navigation.
Routes that are not forwarded:
/api/*— REST API endpoints/q/*— Quarkus dev UI and health endpoints- Paths with file extensions (e.g.
.js,.css,.png)
Everything else returns index.html, and React Router renders the correct page based
on the URL.