GraphQL vs REST: Choose From Client Workloads
Compare REST, GraphQL and a backend-for-frontend using client operations, a worked latency model, cache boundaries, and the cost of operating each option.
The useful question is not whether GraphQL is more modern than REST. It is which API contract makes your actual client operations easier to deliver and operate.
Start with a few representative operations: an account dashboard, a partner order submission, a bulk export or a public catalog lookup. Record what each needs, how often its data shape changes, who controls its clients and what happens when one dependency fails. Those facts are better inputs than a technology preference.
This guide compares API choices for existing software and backend systems. It does not assume that introducing GraphQL requires federation, replacing every endpoint, or rebuilding client applications.
Separate three decisions
The public contract, the aggregation layer and the backend architecture are different choices.
A REST endpoint can aggregate several services. A GraphQL resolver can call an existing REST API. A backend-for-frontend, or BFF, can expose a small set of client-specific operations over HTTP without a query language.
GraphQL provides a typed schema and client-selected fields. REST designs can also offer selectable representations. For example, the JSON:API specification defines sparse fieldsets. That is a design available to an API that implements it, not a feature every REST endpoint already has.
Neither choice removes the need to fetch and authorize the underlying data. Reducing browser requests can move coordination into the server rather than eliminate it.
A worked dashboard comparison
Consider a fictional account dashboard. All timings and payload sizes below are assumptions for a design exercise, not measurements or product claims.
The client first needs account details to discover the IDs used by orders and billing. Account processing takes 20 milliseconds. Orders takes 40; billing takes 30. Each browser-to-API round trip adds 80 milliseconds. The baseline fetches account first, then orders and billing in parallel.
The modeled baseline latency is:
(80 + 20) + max(80 + 40, 80 + 30) = 220 milliseconds.
Now place the orchestration in either a BFF endpoint or a GraphQL operation. Assume 5 milliseconds of aggregation overhead, and treat the stated backend durations as already including server-side dependency communication. The two downstream fetches still run in parallel after account:
80 + 5 + 20 + max(40, 30) = 145 milliseconds.
The improvement in this model comes from moving a dependent browser round trip, not from GraphQL syntax.
| Design | Modeled result | | --- | --- | | Existing REST calls | 3 requests; 220 ms; 60 KB of assumed response bodies | | REST BFF | 1 request; 145 ms; 12 KB shaped for this dashboard | | GraphQL operation | 1 request; 145 ms; 12 KB selected for this dashboard |
The sizes are assumed uncompressed body sizes. Transfer time, connection setup, cache hits, contention and tail latency are excluded from the latency model. Do not add these numbers to a forecast as measured savings.
The operating tradeoff matters. A BFF team owns a specific response and endpoint lifecycle. A GraphQL team owns schema evolution, resolver behavior and controls for the set of queries it permits. If clients only need a few stable views, the BFF may solve the problem with less new machinery. If many supported clients need varying combinations of the same governed data, GraphQL's selection model may justify that machinery.
Prototype both with actual operations before concluding that a query layer will reduce delivery cost.
Choose against constraints, not a universal score
| Situation | A reasonable starting hypothesis | | --- | --- | | Stable partner commands | Explicit HTTP operations with documented errors, idempotency and compatibility | | Public read-heavy catalog | Cacheable resource URLs, unless query flexibility has a demonstrated benefit | | Several changing client views | GraphQL or a BFF, compared using real payloads and change requests | | Long-running export | Asynchronous job API with status and download controls; neither style removes the job lifecycle | | Existing services with different owners | Preserve service ownership and add aggregation only where needed | | Limited graph operating experience | Pilot a bounded surface before placing critical operations behind a new runtime |
These are hypotheses, not prohibitions. A GraphQL mutation can implement a business command, and a REST API can serve varied clients well. Check tooling, access control and operational support in your environment.
Avoid adopting federation simply because multiple teams contribute code. It adds schema composition and cross-service planning responsibilities that a single graph may not need.
Design caching around data visibility
GraphQL is not limited to POST. The GraphQL-over-HTTP draft describes GET queries and prohibits mutations over GET. Check the behavior of your deployed implementation because the transport document is a draft, not a guarantee about every library.
For both styles, a cache design must answer:
- What identifies the operation and its inputs?
- Which tenant or identity can see the response?
- How long is the data reusable, and what invalidates it?
- Which cache is allowed to store it?
- What happens after a permission or underlying data change?
For a public catalog, a shared cache key might include the resource or persisted operation, variables, locale and representation version. For an account balance, shared public caching would usually be the wrong boundary. Use an explicitly designed private or identity-scoped cache, or avoid caching where the freshness and confidentiality requirements demand it.
A GraphQL operation hash alone does not identify variables or access rights. A REST URL alone is not sufficient when a representation varies by user or headers. Apply RFC 9111's caching rules, including authorization and response directives, and test the actual intermediary.
Do not put sensitive variables in GET URLs without assessing logging, history and intermediary exposure. POST does not remove the need to protect request logs either. Neither protocol style provides a blanket confidentiality guarantee.
Account for failure and authorization work
GraphQL may return partial data alongside execution errors. A client must know whether the missing field is optional for its task. HTTP status alone is not enough to decide whether an account dashboard contains all the information it needs.
A REST aggregation endpoint needs the same business decision: return a partial view with an explicit status, fail the operation, or supply a bounded stale value. Design this behavior rather than letting a library default become the customer experience.
Authorization belongs at the resource and business-rule boundary. In GraphQL, verify that aliases, nested fields and alternate traversal paths cannot bypass a rule. In REST, verify equivalent access through list, item, search and export endpoints. A gateway's successful authentication is not proof of permission to access every returned object.
Choose a query budget or operation limit based on measured capacity. GraphQL makes variable query shapes explicit, but REST endpoints with large filters, expansions or exports can also be expensive. Compare worst permitted work, not only normal request count.
Compatibility remains a shared responsibility
A typed schema helps expose contract structure; it does not make every additive change harmless. A newly requested field can introduce latency, permission requirements or a dependency whose failures affect the response. Changing business meaning under an unchanged field name can break a consumer without a schema error.
For either style, record supported clients, deprecation policy, representative contract tests and recovery behavior. Use observed traffic to inform removal, but account for infrequent partner or reporting workloads that may be absent from a short observation window.
GraphQL also needs operation-level usage and error visibility. REST needs meaningful endpoint and business-operation visibility, particularly when several endpoints implement one workflow.
Use a bounded migration experiment
Select one operation with a known pain point. Keep its existing contract available while a controlled client cohort uses the alternative. Compare:
| Evidence | What to capture | | --- | --- | | Client value | Required fields delivered, integration work and clarity of errors | | Performance | End-to-end distributions, backend fan-out and response size | | Operating cost | Compute, cache behavior, tracing and engineering time | | Failure behavior | Slow dependency, denied field, invalid input and partial result | | Reversibility | How the cohort returns to the old contract without data loss |
A read-only pilot is often easier to reverse than a change involving writes and external side effects. If the operation is a command, plan idempotency and state compatibility before routing traffic. Dual execution of a mutation can duplicate the business action.
Define what evidence would justify keeping the alternative and what would make you stop. A faster happy-path demo is insufficient if authorization becomes harder to reason about or the on-call team cannot diagnose partial failures.
Make the smallest defensible choice
Choose REST, GraphQL or a hybrid that fits the operations you can name. Keep a stable partner interface if it works. Add a BFF when one client needs aggregation. Introduce GraphQL when client-driven selection earns its ongoing ownership cost.
If you choose a federated graph, the companion GraphQL operations guide covers query plans, batching, nullability and releases. For an API design review, bring representative client operations and constraints to Ampity's backend systems and API practice, not just a preferred framework.