Designing Thin FastAPI Routes
Keeping FastAPI routes thin by separating screen-response assembly from storage access
The Hangangjari backend consists of a FastAPI process and separate workers.
The API returns screen-ready responses that the app and widgets can read immediately. External data collection, forecast calculation, push candidate selection, and delivery are handled by workers.
The API has to respond quickly when users open the app. Public-data collection can be slow or fail and has a different schedule for each source, so it runs outside the request path.
Early on, reading and composing everything inside route functions seemed sufficient. As park first screens, widgets, forecasts, and notification settings were added, routes had to know external sources, Redis, DB schema, and DTO shapes at the same time. Even small changes then spread across layers.
FastAPI handles HTTP, application queries and use cases assemble screen responses, and repositories select stored rows. This separates “build this park’s first screen” from “select these rows from this table.”
HTTP Routes That Only Assemble
The backend is split into something close to clean architecture.
domain: entities and policies.application: queries, commands, use cases, and ports.infrastructure: SQLAlchemy, Redis, APNs, and external source adapters.interfaces: HTTP routes and job entrypoints.
flowchart TB Route["FastAPI route"] --> Dependency["Dependency layer"] Dependency --> UseCase["Application query / command"] UseCase --> Port["Repository / cache port"] Port --> Infra["SQLAlchemy / Redis / APNs / source client"] Infra --> Store["Postgres / Redis / external service"] UseCase --> DTO["Pydantic DTO serialization"] DTO --> Client["iOS app / widget"]
FastAPI routes stay thin. Once a route starts knowing SQL queries or source adapters directly, screen needs and infrastructure details become mixed.
Routes handle HTTP parameters and response shape. Queries and use cases assemble what the screen asks for. Repository and cache adapters handle storage details. These boundaries made testing and incident analysis easier.
API Groups Used by the App
The v2 app API area read by the app includes:
- Parks/Parking: parks, parking lots, parking overview, and lot status.
- Home Summary: screen-ready first-screen response that bundles parking, outing, forecasts, and refresh state.
- Outing: per-park general overview and signal detail.
- Forecast: parking forecast, park congestion forecast, and horizon timeline.
- Telemetry: client performance events and product event batches.
- Push: APNs subscription registration and removal.
- App access bootstrap: app access validation and request proof issuance.
Admin routes are separated from user-facing APIs. Admin details do not leak into screen-ready responses.
Data Behind the Parking Screen
The parking overview uses Redis and Postgres together. Master data is read from Postgres, and latest status checks Redis first.
sequenceDiagram
autonumber
participant Client as iOS app/widget
participant Route as FastAPI route
participant Query as Parking query
participant Cache as Status cache
participant Repo as Parking repository
participant DB as Postgres
Client->>Route: Request parking screen
Route->>Query: Build parking screen values
Query->>Repo: Read park and parking-lot reference data
Repo->>DB: Query reference data
Query->>Cache: Check latest parking status
alt Cache miss or malformed entry
Query->>Repo: Check last stored status
Repo->>DB: Query status history
end
Query-->>Route: Return screen-ready read model
Route-->>Client: Return response with freshness
The first screen is handled by home-summary, an endpoint that bundles several data types into one payload.
sequenceDiagram
autonumber
participant Client as App/widget
participant Route as home-summary route
participant Cache as Hot/stale cache
participant Parking as Parking query
participant Outing as Outing query
participant Forecast as Forecast query
participant DB as Postgres
Client->>Route: Request first screen
Route->>Cache: Check ready first-screen cache
alt Hot cache hit
Cache-->>Route: Cached first-screen response
else Cache miss
Route->>Parking: Build parking summary
Route->>Outing: Gather outing signals and source status
Route->>Forecast: Check forecast summary
Parking->>DB: Read parking reference data
Outing->>DB: Read outing reference data
Forecast->>Cache: Read forecast response
Route->>Cache: Store hot response and last-success value
end
Route-->>Client: Return first-screen response
This API simplifies the app first screen and general park widget. The client does not need to compose several APIs in sequence.
home-summary reduced call count and made the app and widget read the same values.
If the client combines several APIs, parking may be fresh while event information is old, or the widget may receive only part of the values and change the meaning of the screen. The server response aligns those differences at once.
Values and Refresh State in One Response
Swift decoding in the iOS app is directly affected by server response changes. DTOs control the change boundary between the internal DB schema and the app’s response format.
DTOs handle these jobs.
- Make optional fields and default policy explicit.
- Keep datetime serialization consistent.
- Distinguish
fresh,stale, andunavailable. - Include source refresh state and generated/observed/fetched timestamps.
- Hide raw payloads and internal policy values.
- Make contracts harder to break while mixed app versions are in use.
App Verification on Read Requests
Current v2 app routes pass app access validation. The concrete platform validation implementation and request proof issuance are separated into the bootstrap side.
The API applies these policies before platform-specific validation details:
- Read APIs can also be abused.
- Write APIs validate request proof more strictly.
- Authentication failure metrics are kept, but user-identifying information is not written directly to logs.
- Operational metric endpoints stay invisible to normal user traffic.
What Thin Routes Changed
Thin FastAPI routes keep screen needs separate from storage details. App APIs follow user-facing screen units, while workers handle collection and calculation.
Each public-data value travels with its source, time, and refresh state. As the first screen became more complex, letting the server bundle screen-ready responses also helped client stability.
Routes own HTTP shapes, use cases own screen assembly, and repositories own stored rows. A screen-response change can therefore stay separate from a storage-implementation change.
Comments
No comments yet. Be the first to leave one.
Pending review