Appearance
OpenAPI Workflow
TT Time Tracker maintains end-to-end type safety between the NestJS API and the Vue frontend through an OpenAPI-driven code generation pipeline.
The pipeline
NestJS controllers + DTOs
│
│ (Swagger decorators)
▼
OpenAPI spec (JSON)
│
│ pnpm openapi
▼
src/api/sdk.gen.ts
(typed functions, one per endpoint)
│
│ import { Entries } from '@/api/sdk.gen'
▼
Vue componentsStep 1 — Annotate with Swagger
NestJS generates the OpenAPI spec from decorators in controllers and DTOs:
typescript
@ApiTags('Entries')
@Controller('tenants/:tenantId/entries')
export class EntryController {
@Get()
@ApiOkResponse({ type: [EntryDto] })
findAll(): Promise<EntryDto[]> { ... }
}The spec is served at GET /docs-json by the running API.
Step 2 — Export the spec
Commit a snapshot of the spec to openapi.json so the generator doesn't require a running API in CI:
bash
pnpm openapi:export
# hits http://localhost:3000/docs-json → writes openapi.jsonThis file is committed to the repository and updated whenever the API changes.
Step 3 — Generate the client
bash
pnpm openapi
# reads openapi.json → writes src/api/The generator (@hey-api/openapi-ts) produces one namespace per Swagger tag, with fully typed request and response shapes.
Why not tRPC or GraphQL?
tRPC requires TypeScript on both ends and tight coupling between the server and client through a shared type package. This works well for monorepos but adds complexity to the build pipeline and makes it harder to call the API from non-TypeScript clients.
GraphQL solves a different problem — flexible querying of complex graphs. TT Time Tracker's API is a straightforward REST CRUD surface. GraphQL's overhead (schema definition, resolvers, N+1 considerations) is not justified here.
OpenAPI gives us:
- Standard HTTP semantics (cacheable GETs, idempotent PUTs)
- A Swagger UI for easy exploration and manual testing
- The ability to call the API from any HTTP client (including external integrations via API keys)
- Type-safe generated clients without coupling the frontend and backend build pipelines
The main downside is the manual export step. If you forget to run pnpm openapi:export after changing the API, the frontend SDK is stale until the next export. This is a low friction cost in practice.