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, and unavailable.
  • 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

Comments

    Image preview