Serve the API as GraphQL with async-graphql on Axum
Decision
Expose the backend to the iOS app as a GraphQL API served by async-graphql on Axum. We derive the schema from Rust types with async-graphql's derive macros, and each domain module contributes its own resolvers (ADR-0009). Subscriptions go over HTTP multipart responses, which Apollo iOS supports, instead of WebSockets.
Why
The iOS app treats Apollo's normalized cache as the source of truth for everything except the trip (ADR-0003). Each view declares the fields it needs in its own query (ADR-0031). The cache deduplicates the results by object identity, and watched queries push updates to every view that reads a changed object. That model depends on GraphQL's typed selections and object identities.
async-graphql integrates with Axum directly and includes subscriptions. Its derive macros let each domain module declare its part of the schema next to its types.
Rejected alternatives
- REST. It is the default, but Apollo's normalized cache and per-view queries need a GraphQL schema, and REST would leave the client to write its own cache.
- juniper. It fits Axum less well, and async-graphql's derive macros suit a backend organised by domain better.
Consequences
A schema change surfaces as a Rust compile error at the resolver instead of a runtime mismatch. GraphQL brings more machinery than REST, such as the multipart subscription transport and the discipline of schema evolution (ADR-0008), and it couples the client more tightly to the server's schema.