The REST vs GraphQL debate is usually framed as if there were a winner. There is not: they are two different ways of writing the contract between whoever publishes the data and whoever consumes it, and each one moves the work to a different place in the architecture.
This guide frames the decision in business language: what actually changes between one style and the other, which symptoms signal that the current contract no longer holds, what you gain and what you pay on each path, and how the choice maps to AWS managed services.
The contract is the decision, not the technology
In a REST API the server publishes a set of addresses and each one returns a structure defined in advance. The client requests /customers/123 and receives the full customer, exactly as the server decided a customer looks. If it also needs the latest orders, it requests another address. The shape of the response is controlled by whoever publishes the API.
In a GraphQL API the server publishes a schema: the full catalog of available data, its types and its relationships. The client writes into each call which fields it wants and from which entities, and receives exactly that. The shape of the response is controlled by whoever consumes the API.
That shift — from server to client — is the whole decision. Everything else is a consequence.
The two symptoms that precede the change
Before debating styles it helps to recognize the symptoms. There are two, and they are felt in the application, not in the diagram:
- Over-fetching: more arrives than needed. The screen needs the customer’s name and balance, but the response also carries their address, contact history and preferences. You pay for transfer, you pay for processing and you pay for battery on the device, all for data nobody looks at. On an average mobile network this is noticeable.
- Under-fetching: one call is not enough. Assembling an order detail means calling the order, then the customer, then each product. Every call adds its own latency, and the screen ends up as slow as the sum of all of them.
When these two symptoms show up together and systematically, the problem is no longer solved by adding new addresses: the contract was designed wrong.
Caching: the most underestimated difference
This is the dimension where REST keeps a structural advantage that many comparisons leave out.
In REST, each address identifies a resource and the response travels with standard HTTP headers. That means the browser, the content delivery network and any intermediate layer can cache the response without knowing anything about the application. It is free caching, inherited from how the web works.
In GraphQL queries arrive through a single entry point with a variable body, so that infrastructure can no longer decide on its own what to store. Caching moves into the application layer: per-entity caches, resolver result caches, or platform-managed caching. It is perfectly solvable — AWS AppSync offers server-side caching as a capability of the service — but it is work that REST did not require.
The practical reading: if a good share of your API performance today comes from address-level caching, moving to GraphQL without designing the equivalent cache strategy makes the outcome worse.
Versioning: two ways of not breaking anyone
In REST the established pattern is to publish a new version of the address and keep the previous one alive while consumers migrate. It is explicit and easy to communicate, and it also accumulates: every live version is code that has to be maintained and tested.
In GraphQL the endpoint is not versioned: the schema evolves. Fields and types are added without touching existing ones, and those slated for removal are marked as deprecated so clients drop them gradually. The advantage is that there are no parallel versions. The condition is knowing which field each client consumes — without that visibility, retiring a field is a gamble.
Observability and query cost control
Here the asymmetry flips, and it deserves to be said plainly.
In REST, each address is a natural unit of measurement: you know how many times it was called, how long it took and how often it failed, and you can set a per-client limit on that address. The operational reading is direct.
In GraphQL, every query enters through the same point. Without per-field and per-resolver instrumentation, the dashboard shows a single endpoint with an average latency and says nothing useful. Worse: one badly written query from a client can request deeply nested relationships and make the back end work far harder than a hundred REST calls. That is why a healthy GraphQL operation needs, from day one, per-field metrics, depth and query complexity limits, and per-resolver timeouts. It is not optional: it is the condition for operating.
Security: the surface changes shape
Both styles share authentication and authorization mechanisms — federated identity, tokens, custom authorizers — but the surface to protect is different.
In REST, authorization applies naturally per address and method: who can read a resource, who can modify it. In GraphQL, authorization has to apply per field, because a single query can traverse several entities with different sensitivity levels. Authorizing only the schema entry point leaves the door open for an authenticated client to navigate relationships that are not theirs.
Two GraphQL-specific controls sit on top of that: limiting nesting depth to prevent queries that explode combinatorially, and deciding whether schema introspection stays exposed in production.
REST vs GraphQL: dimension by dimension
| Dimension | REST | GraphQL |
|---|---|---|
| Who defines the response | The server, per address | The client, per query |
| Calls to assemble a view | Several, chained | A single one |
| Over-fetching / under-fetching | Frequent by design | Avoided by design |
| Caching | Native to HTTP, no code | Application layer, designed |
| Contract evolution | Parallel versions | Evolving schema with deprecated fields |
| Authorization | Per address and method | Per schema field |
| Observability | Direct, per address | Requires per-field and per-resolver metrics |
| Managed AWS service | Amazon API Gateway | AWS AppSync |
How each style is delivered on AWS
Neither path requires operating your own API servers.
Amazon API Gateway covers the REST style with two variants that are not equivalent. REST APIs bring the full set of management capabilities: API keys, per-client usage limits, request validation, cached responses, AWS WAF integration, private endpoints and tracing with AWS X-Ray. HTTP APIs offer a reduced feature set — they do not include caching, API keys or AWS WAF integration, for example — in exchange for a simpler model. The AWS documentation is explicit: if you need API keys, per-client throttling, request validation, AWS WAF or private endpoints, the right variant is REST API.
AWS AppSync covers the GraphQL style. It exposes a single endpoint that can read from multiple data sources at once, allows combining several GraphQL APIs into one merged API, resolves real-time subscriptions and publish-subscribe channels over managed WebSockets, offers server-side caching, and brings authorization controls with API keys, IAM, Amazon Cognito, OpenID Connect providers and custom authorizers with AWS Lambda. Resolver logic is written in JavaScript and TypeScript.
Both services integrate with AWS Lambda, with AWS services and with existing back ends, so the choice of style does not dictate where your business logic lives.
The decision tree, without romanticism
Stay with REST when:
- The API is public and consumed by third parties who expect the industry-standard contract.
- The integration is system to system, with a handful of stable operations.
- Address-level caching explains a good share of your current performance.
- The team does not yet have the operational practice to govern a shared schema.
Consider GraphQL when:
- Several clients — web, mobile, partners — consume the same data with different needs.
- Assembling a screen systematically requires chaining three or more calls.
- The front end team is blocked waiting for new addresses from the back end.
- The application needs real-time updates as part of the product, not as an add-on.
And the most common path in organizations with legacy systems is not choosing: it is keeping each domain’s REST APIs as the source of truth and publishing a GraphQL layer on top that composes that data for user-facing applications. It works well as long as the schema has an owner and governance, and does not grow by accumulation.
How we approach this at Caleidos
When we run an application modernization, the API layer is decided with the same criteria as the rest of the architecture: what real consumption pattern the clients have, what the operation can sustain and which team will support it. We do not migrate an entire contract because the new style is more modern; we start with the domain where the symptom is felt and measure before extending.
If you are still getting the fundamentals in order, the guide on what an API is explains the mechanism from scratch, and what microservices are shows why the API conversation and the service architecture conversation always arrive together.
Frequently asked questions
What is the difference between REST and GraphQL? Who decides the shape of the response. In REST the server defines it per address; in GraphQL the client defines it per query against a published schema.
Does GraphQL replace REST? No. It handles the case of many clients with different needs over the same data better. REST remains the right answer for public APIs, simple integrations and scenarios where address-level caching delivers the performance.
What do you lose moving to GraphQL? Free HTTP caching and the direct operational reading per address. Both are recoverable, but through explicit design and work.
Which AWS services do I use? Amazon API Gateway for REST — the REST API variant if you need keys, per-client limits, validation, AWS WAF or private endpoints — and AWS AppSync for GraphQL.
Want to decide your API layer with your own case on the table?
Let’s talk about your case: in 30 minutes we review how your applications consume data today, where the wrong contract is being paid for, and which path puts that layer in order without stopping what is already in production.