Files
bible/rules/patterns/go-api/contract.md
T
Mikhail ChusavitinandClaude Opus 5 59e3107197 Add timeout, busy-state, parser-variant and build-freshness rules
- task-discipline: require explicit target platform/language before implementation
- go-api: bounded context deadline on every outbound network/DB call
- controls-selection: visible busy state for any control triggering such a call
- import-export: enumerate real format variants before writing a parser
- app-binary: embedded assets do not hot-reload; verify rebuild before diagnosing

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 21:00:42 +03:00

3.3 KiB

Contract: REST API Conventions (Go Web Applications)

Version: 1.2

URL Naming

  • Resources are plural nouns: /api/assets, /api/components, /api/pricelists.
  • Nested resources use parent path: /api/assets/:id/components.
  • Actions that are not CRUD use a verb suffix: /api/pricelists/:id/export-csv, /api/tasks/:id/cancel, /api/sync/push.
  • No verbs in resource paths: use /api/assets + DELETE, not /api/delete-asset.

HTTP Methods and Status Codes

Operation Method Success Notes
List GET 200
Get one GET 200 / 404
Create POST 201 Return created resource
Update PUT / PATCH 200 Return updated resource
Delete / Archive DELETE 200 or 204
Async action POST 202 Return {task_id}
Validation error POST/PUT 422 Return field errors
Server error any 500 Log full error server-side
Not found any 404
Unauthorized any 401
  • Never return 200 OK for validation errors — use 422 Unprocessable Entity.
  • Never return 200 OK with {"error": "..."} in the body — use the correct status code.

Error Response Format

All non-2xx responses return a consistent JSON body:

{
  "error": "human-readable message",
  "fields": {
    "serial_number": "Serial number is required",
    "price": "Must be greater than 0"
  }
}
  • error — always present, describes what went wrong.
  • fields — optional, present only for validation errors (422). Keys match form field names.
  • Never expose raw Go error strings, stack traces, or SQL errors to the client.

List Response Format

{
  "items": [...],
  "total_count": 342,
  "page": 2,
  "per_page": 50,
  "total_pages": 7
}
  • Always include pagination metadata even if the client did not request it.
  • items is always an array — never null, use [] for empty.

Request Conventions

  • Accept application/json for API endpoints.
  • HTML form submissions (htmx) use application/x-www-form-urlencoded or multipart/form-data.
  • File uploads use multipart/form-data.
  • Query parameters for filtering and pagination: ?page=1&per_page=50&search=abc&status=active.

Timeouts

  • Every outbound network or database call must run under a bounded context.Context deadline. No call reachable from a user action may block indefinitely.
  • The deadline is set by the handler or service that owns the request, not by the caller of the app.
  • On timeout, return a real error status (504 for an upstream timeout, 500 for an internal one) with a human-readable message. Never let a timed-out call fall through as an empty success.
  • The UI side of the same rule — visible busy state for the duration of the call — is owned by controls-selection.

Health and Utility Endpoints

Every application must expose:

GET /health       → 200 {"status": "ok"}
GET /api/db-status → 200 {"ok": true} or 500 {"ok": false, "error": "..."}
  • /health must respond even if DB is down (for load balancer probes).
  • /api/db-status checks the actual DB connection and returns its state.

Async Actions

Long-running operations return 202 {"task_id": "..."} immediately and the client polls GET /api/tasks/:id. Full flow, statuses, and task struct: go-background-tasks contract.