API Versioning β
The backend uses NestJS URI versioning, enabled in apps/agri-backend/src/main.ts:
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: VERSION_NEUTRAL,
});The versioning engine is live, but no route is prefixed yet. Every controller answers at its current bare path (/workspaces, /fields, /admin/users, β¦). The bare path is the implicit v1.
Why version-neutral (not /v1 everywhere) β
Adding a /v1 prefix to a currently-unversioned API is itself a breaking change β every existing client pins bare paths:
| Client | Pins bare path because⦠|
|---|---|
| Web PWA | VITE_API_URL is inlined at build time; a stale service-worker bundle 404s until reload |
| Installed mobile | Old APKs can't be force-migrated |
| Partner API + MCP | Hit bare paths (/workspaces, /public/*) |
VERSION_NEUTRAL puts the engine in place with zero break to any of them, so the first real breaking change can ship cleanly. It is not a dead end β see the rollout below.
Adding a breaking change (introducing v2) β
When a route needs a backwards-incompatible change, version it explicitly instead of mutating the existing handler:
@Controller('workspaces')
export class WorkspaceController {
@Version('2') // β GET /v2/workspaces
@Get()
async listV2(): Promise<WorkspaceResponseV2[]> { ... }
// old shape keeps answering at the bare path
@Get()
async list(): Promise<WorkspaceResponse[]> { ... }
}@Version on a handler/controller overrides the global default, so the v2 handler answers only at /v2/... while the untouched handler stays bare.
Rollout phases β
| Phase | defaultVersion | Bare paths | /v1 | Client action |
|---|---|---|---|---|
| 1 | VERSION_NEUTRAL | β live | β | none |
| 2 | ['1', VERSION_NEUTRAL] | β live | β aliased | move baseUrl to /v1 (single env each: VITE_API_URL, EXPO_PUBLIC_API_URL, MCP TELLIA_API_URL); ship new mobile build |
| 3 | '1' | β 404 | β mandatory | all clients already on /v1; sunset gated on bare traffic dropping to ~0 (watch via OtelRouteInterceptor) |
The Phase 2 flip is one line and retroactive β changing defaultVersion to the array re-registers all routes at both bare and /v1 with no per-controller edits.
Out of scope β
setGlobalPrefixβ separate concern./api/openapi.jsonis a raw Express route (main.ts), outside the Nest router, so it is unaffected by versioning.- WebSocket
/wsβ the Socket.IO gateway is outside HTTP versioning. Version via the event payload if ever needed.
Related Pages β
- Partner API Docs (Scalar) β the OpenAPI spec consumed by partners
- Backend Overview β request lifecycle and module map