Instruction file imported from SteveDraper/Planets-Console (
.cursor/rules/bff.mdc). Copyright stays with the author.
BFF Layer Conventions
The BFF aggregates and reshapes responses from the Core REST API into shapes convenient for the SPA. It has no business logic of its own. See core-api.mdc for the layer below.
Router Conventions
- One
APIRouterper domain resource inpackages/bff/routers/ - All routes use prefix
/bff/... - Registered in
packages/bff/app.py, which assembles the BFF FastAPI sub-app - The frontend codegen source is a dedicated BFF-only OpenAPI schema endpoint, for example
/bff/openapi.json, rather than the whole application's/openapi.json
# packages/bff/routers/systems.py
router = APIRouter(prefix="/bff/systems", tags=["bff-systems"])
@router.get("/{id}")
def get_system_view(id: str, svc: SystemService = Depends(get_system_service)):
system = svc.get(id) # calls Core REST API service
return shape_for_frontend(system) # reshape here, not in the service
Preferred App Composition
The preferred implementation is:
packages/api/app.pybuilds the Core REST API sub-apppackages/bff/app.pybuilds the BFF sub-apppackages/server/app.pybuilds the root server app and mounts them into a single process, e.g./apiand/bff
This is preferred over filtering a single app's schema because the BFF-only OpenAPI surface then exists by construction rather than by custom schema logic.
Transport Schemas
The BFF may reuse upstream domain dataclasses only when they already match the frontend contract exactly. In practice, BFF responses will often diverge from domain models because the BFF is responsible for SPA-oriented shaping.
- Prefer dedicated response models when the BFF renames fields, combines resources, omits internals, or adds UI-oriented grouping
- Keep these transport models close to the BFF router or in a small
packages/bff/transport/module if they are shared across routes - The BFF should be explicit about what contract it exposes; do not let frontend-facing response shapes emerge accidentally from domain internals
Exceptions
- See
server-exceptions.mdcfor the exception hierarchy and HTTP handling rules. All exceptions raised by the BFF must inherit fromBFFError(inbff.errors). The BFF app registers a global exception handler viamake_http_exception_handler(BFFError)(fromapi.errors).
Layer Boundary Rules
- No business logic — the BFF may filter, aggregate, and rename fields, but must not compute derived state that belongs in the domain model
- No direct storage access — the BFF calls Core REST API services; it never touches
StorageBackend - Allowed Core REST API import surface — import from
packages/api/services/for business services; import frompackages/api/errors(e.g.api.errors) forPlanetsConsoleErrorandmake_http_exception_handleronly. Do not import frommodels/,storage/, or serialization internals
OpenAPI Spec & Type Codegen
The BFF exposes a dedicated OpenAPI schema endpoint, such as /bff/openapi.json. This BFF-only schema is the source of truth for BFF→frontend type safety — TypeScript types for the frontend are generated from it rather than written by hand.
Generating types
From the repo root (or packages/frontend/ via npm):
make generate
# or: cd packages/frontend && npm run generate:api
Pipeline: dump full BFF OpenAPI from bff.app → scripts/filter_bff_openapi.py (per-router slices) → openapi-typescript emits committed src/api/schema-<slice>.ts for v1 slices (games, analytics, shell, diagnostics). Intermediate JSON (.bff-openapi.json, .bff-openapi-slices/) is gitignored.
Application code imports the smallest schema-<slice>.ts only (see ADR 0003). Do not add monolithic or barrel schema.ts; CI fails if src/api/schema.ts exists (make check_frontend_api_no_monolithic_schema, part of make ci).
- Regenerate whenever BFF response shapes change
- Commit generated TypeScript — never hand-edit
schema-*.ts - CI and local pre-merge:
make check_frontend_api_slices(orcd packages/frontend && npm run check:api:slices) — same dump/filter pipeline as generate, thenopenapi-typescript --checkper slice; plusmake check_frontend_api_no_monolithic_schemato block monolithicschema.ts - If the app also exposes a full-process schema, do not use it for frontend codegen
Live server fallback (fetch OpenAPI JSON, then filter + slices):
cd packages/frontend && npm run generate:api:live
Testing
- BFF tests verify response shaping, aggregation, and contract stability
- BFF tests do not re-test Core REST API business rules; mock service calls where practical
- Include schema-sensitive tests for routes whose response contracts are consumed heavily by the frontend