Conventions
API path conventions
All HTTP routes in microservices follow standardized path conventions so the BE's MCP Server can dispatch generic CRUD tools without per-entity configuration.
Route handlers are factory-produced (MANDATORY): the service's whole
HTTP surface is declared ONCE via the SDK's makeServiceRoutes — entity
declarations become makeEntityRouter + entityCrudSpec, RpcRoute[]
tables become makeRpcRouter + rpcSpec, and /api/v1/openapi.json
via makeOpenApiHandler — all derived from that single declaration, so
the published spec can never drift from the registered routes.
createMicroservice refuses to boot with a hand-rolled
(req, res, url) => boolean handler — auth, RBAC, body validation and
RFC 7807 errors are enforced by the factory, not by convention.
Entity CRUD (/api/v1/entities/:entity/...)
Database-backed entities with standard CRUD lifecycle use this pattern:
Code
Rules:
:entityis the snake_case singular noun (e.g.provider,config_entry). Never plural, never camelCase.:uuidis always the UUID path parameter.- Not all entities support all operations — unsupported operations simply don't register that route.
- The
metaendpoint returns the entity's field schema, consumed by the MCPget_entity_metatool and the FE for dynamic form generation. - Write-payload standard (
{entity}envelope): single-entityPOST/PUTbodies MUST be{ entity: {...} }— flat entity fields at the body root are rejected with400(hard break, same contract as the BE). Atranslationssibling is NOT accepted on microservice entities (no translations table in the service schema). Bulk endpoints (bulk-*), actions,DELETEand RPC routes keep their own bodies. The MCP proxy dispatch already wraps tool args into{ entity }— no per-tool change needed.
Example — list providers through the BE proxy:
Code
Service actions (/api/v1/actions/:action)
Non-CRUD business actions:
Code
:actionis a snake_case verb-noun.- These are NOT exposed as MCP generic CRUD tools.
Webhooks (/webhook or /webhook/:identifier)
External callbacks (e.g. Brevo™ delivery events):
Code
- Webhooks use API key authentication, NOT JWT.
- Webhook paths do NOT use the
/api/v1/prefix.
System / Health
Code
OpenAPI spec requirements
Every microservice MUST export a complete OpenAPI 3.x spec at
GET /api/v1/openapi.json:
- List ALL implemented routes
- Use
operationIdin snake_case for every operation - Include
tagsgrouping operations by entity or category - Include
summaryanddescriptionfor every operation - Use
snake_casefield names in request/response schemas
Data model rules
Naming convention
| Layer | Name form | Example |
|---|---|---|
| PG table | snake_case plural | providers, config_entries, sender_logs |
| TS entity class | PascalCase singular | ProviderEntity, ConfigEntryEntity |
| API URL entity segment | snake_case singular | /api/v1/entities/provider/... |
- Entity class = singular (one row schema)
- PG table = plural (collection of rows)
- URL entity segment = singular (resource type)
Snake_case everywhere
DB columns, TS interfaces, JSON request bodies, and JSON response bodies
ALL use snake_case. A field named from_email in the DB is
from_email in the TS interface and from_email in the JSON response.
Never rename fields between layers.
Exception: External API adapters (e.g. Brevo™ expecting camelCase). The translation happens ONLY at the adapter boundary.
No DTO transformation
The DB row IS the TS model. Do not create intermediate DTO classes that
rename fields. Prefer spreading raw results (return { ...settings })
over field-by-field rebuilding.
No fake defaults on the read path
- Forbidden: lowercasing, uppercasing, trimming on the read path
- Forbidden: fallback string-literal defaults (
|| "...",?? "") for configuration data - A value either exists in the DB or it doesn't —
undefinedif missing,nullif the row exists but value is NULL,stringif present - Mandatory-field checks throw before the return, they don't fake a default
Type conversions are allowed
string(DB) →boolean(TS) via=== "true"— allowedstring(DB) →enum(TS) via validation + normalization — allowed
These are type conversions, not data-quality enforcement. Data quality is enforced at the write path (API upsert validation).
Package versioning
All package versions in package.json MUST be pinned to exact versions
(e.g. "typescript": "5.9.3"). NO ranges (^, ~, >=, *, latest)
are allowed for registry packages. This ensures every dev machine, CI
build, and production rebuild gets the exact same dependency tree.
Workspace dependencies use workspace:* (e.g.
"@primebrick/sdk": "workspace:*").
Repo tooling
| Tool | Where | Purpose |
|---|---|---|
scripts/version-sync.mjs | repo root | version:auto/prebuild hook — keeps version aligned across service package.jsons |
<svc>/scripts/database-patch-apply.ts | per service | db:migrate — runs the dal-pg runMigrations engine against the service's own schema (same code path as boot) |
<svc>/scripts/build-docs-pack.ts | per service | generate:docs — builds dist/docs-pack (SDK buildDocsPack) |
Next steps
- Architecture — NATS™ bus, BE proxy, SDK lifecycle
- EmailSender — Email sending microservice