Skip to content

API Versioning ​

The backend uses NestJS URI versioning, enabled in apps/agri-backend/src/main.ts:

typescript
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:

ClientPins bare path because…
Web PWAVITE_API_URL is inlined at build time; a stale service-worker bundle 404s until reload
Installed mobileOld APKs can't be force-migrated
Partner API + MCPHit 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:

typescript
@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 ​

PhasedefaultVersionBare paths/v1Client action
1VERSION_NEUTRALβœ… liveβ€”none
2['1', VERSION_NEUTRAL]βœ… liveβœ… aliasedmove baseUrl to /v1 (single env each: VITE_API_URL, EXPO_PUBLIC_API_URL, MCP TELLIA_API_URL); ship new mobile build
3'1'❌ 404βœ… mandatoryall 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.json is 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.