How REST and GraphQL Draw the API Boundary
From the front end, a profile screen on a code-hosting service looks simple: show a user’s name, list a few of their repositories, and include each one’s star count. The API boundary decides where that screen gets assembled, and REST and GraphQL draw it in different places.
REST is the established, widely familiar style for building web application programming interfaces (APIs); GraphQL, publicly released by Facebook in 2015 and adopted more widely in the years since, is a newer alternative built on a different premise. Lining the two up as rival techniques is tempting, but they are not defined at the same layer: Representational State Transfer (REST) is an architectural style, a set of constraints on how a client and server exchange representations of resources over a protocol such as HTTP,1 while GraphQL is a query language and runtime in which a typed schema describes what a service can return and each request states which parts it wants. Here, REST means the resource-oriented HTTP style common in API design discussions, not every possible interpretation of Fielding’s constraints or every way to model resources over HTTP.
REST places addressable resources at that boundary and lets the server define their representations, sometimes with negotiated or parameterized variants, so the API’s variation lives mostly across URLs and resource shapes. GraphQL places a single queryable schema there and lets the client select from it, so the variation lives in the query. That placement shows up in how responses are cached, how the API is described and grown, and how much control the server keeps over its own workload.
Resources and fields
A REST resource is something the server has made addressable. Users, orders, and repositories can each have URLs, and a request to one of those URLs returns a representation the server designed. Some APIs offer sparse fieldsets, embedded resources, or expand= parameters, but the server still decides which shapes exist. Reading related data means following the paths the server laid out, usually across several resources, and each representation may carry fields the caller does not use.
GraphQL exposes fields on types rather than representations at URLs. The schema is a graph: a User type carries a name field and a repositories field that leads to Repository types, which carry fields of their own. A query walks that graph, names the fields it wants, and gets back a response in the same shape.
query {
user(id: "123") {
name
repositories(first: 3) {
name
stars
}
}
}
One request returns the user’s name and three repositories, each with only its name and star count, in the shape of the query. In a conventional endpoint-per-resource design, the same result takes at least two requests, one for the user and one for the repositories, each returning a server-defined representation the client then trims. A resource API can add a more specialized endpoint or embed the related repositories, but that is the same boundary decision in another form: the server is adding another representation shape so the client does not have to assemble it.
The familiar symptoms of the contrast all trace back to one question: whether the server composes the response or the client does. Over-fetching is a representation carrying more than the caller needs; under-fetching is a representation so lean the caller must issue further requests to finish a screen; endpoint count often grows when the server adds tailored representations for recurring client needs. A controlled experiment in which participants implemented the same queries against GitHub’s API in both styles found the GraphQL versions quicker to write, and more so for queries that in REST would span more endpoints; behind the timings is the plainer point that selecting fields sits closer to describing data than orchestrating the calls that gather it.2
What HTTP already provides
Putting resources at URLs does more than address them: it states the API’s boundary in the terms the web’s own infrastructure already speaks. A GET response names its resource by URL, so it can be stored and reused by anything on the path: the browser, a shared proxy, a content delivery network at the edge. A validator such as an ETag, a fingerprint of a representation, lets a client revalidate with a conditional request and receive an inexpensive 304 Not Modified when nothing has changed. Methods carry intent and status codes carry outcome, and intermediaries act on both without knowing anything about the application. Cacheability and statelessness are constraints of the style precisely so that this reuse is possible.
GraphQL weakens that generic layer unless the deployment deliberately restores stable operation identities. Queries vary, they often travel by POST to one endpoint, and a request body opaque to shared caches gives them no URL-shaped key to store against. The web’s caching tier has less to work with by default. What REST receives from the protocol, GraphQL usually rebuilds above it, caching by object identity in client stores like Apollo Client and Relay, and giving recurring operations stable hashes through persisted queries so they can be cached and even sent over GET.
The same shift appears in errors. HTTP still matters for transport failures, authentication failures, malformed requests, and server failures, but application-level GraphQL execution errors commonly appear in a response body with an errors field, often alongside partial data. Generic clients and proxies therefore see less of the application outcome in the status code than they would with a resource API that maps those outcomes directly onto HTTP. The precision of a client-composed response is paid for with generic HTTP machinery that a uniform, cacheable interface had supplied.
The schema as contract
The schema that makes field selection possible is also the API’s contract, and unlike most resource API contracts it runs inside the system. A GraphQL server can answer queries about its own schema, so tools read the types, complete a query as it is typed, and validate it before it executes; the API describes itself in band. A REST service can publish a similar description through OpenAPI, and that document may be generated from code or used to drive contract-first development, but it is still a separate artifact beside the resource interface. The hypermedia links meant to let REST responses advertise their own next steps are part of the style’s original design, but they are also the part least often built in everyday resource APIs.
One schema also changes how an API grows. Types and fields can be added without disturbing existing queries, so GraphQL favors additive change and marks a retired field @deprecated rather than minting a parallel URL like /v2. The price of that is a schema the server wholly owns and has to keep honest: every reachable field is one it has promised to resolve, and deprecated fields linger until usage data shows that no client still asks for them. Each field is also a point of access: authorization has to account for field-level selection, often closer to resolver execution or service-layer policy than at a URL/method boundary.
This comparison is mostly about reads, where field selection changes the shape and amount of data returned. Writes draw the boundary differently too. REST exposes intent through methods such as POST, PUT, PATCH, and DELETE, with idempotence and status codes carrying part of the contract. GraphQL collects writes under mutations: still typed and schema-visible, but less directly legible to generic HTTP infrastructure. Real-time delivery is a separate transport question.
The load a client can impose
Letting the client compose the response reshapes the server’s workload twice over. First, the assembly the client no longer performs falls to the server’s resolvers, the server-side functions behind schema fields. A query descending from a user to repositories to collaborators can issue one backend call per node it visits, the N+1 problem that batching utilities such as DataLoader exist to fold back into fewer calls.
Second, the client gains a say in how much a request costs at all. A short query can ask for deeply nested and repeated fields whose result dwarfs the text that requested it, and a formal study of GraphQL’s semantics shows how quickly a response can grow relative to the query behind it.3
Resource APIs can be expensive too, especially when filters, joins, searches, or expansion parameters reach into large datasets, but each endpoint exposes a smaller set of shapes the server has chosen. A query endpoint exposes a broader operation space, so the bound has to be restored by other means: scoring each query by depth or complexity and refusing the expensive ones, capping nesting, or admitting only a fixed allowlist of persisted queries in production. The flexibility that removes over-fetching is the same flexibility that turns an arbitrary query into a capacity question.
Where composition lives
REST and GraphQL are not two implementations of one idea. They draw the client-server boundary around different units. REST keeps the boundary in HTTP’s own terms and inherits the web’s caching and intermediaries without writing most of that machinery again, in exchange for representation shapes the server has to anticipate. GraphQL moves composition to the client and gains a typed, self-describing graph that clients can query precisely, in exchange for rebuilding parts of the infrastructure REST borrowed and policing the load a query can impose.
Neither exchange is the better one in the abstract; which one to accept depends on the pressures a given API faces. Choose GraphQL when one data graph feeds many unlike clients, when screens frequently need cross-resource slices of data, or when front ends change faster than server-shaped representations can comfortably follow. Choose resource-oriented HTTP when the useful representations are stable, coarse-grained, cacheable, and valuable to expose in the same terms browsers, proxies, CDNs, and generic clients already understand. Each difference above, in how responses are cached, how the schema is described and grown, and how much a request can cost the server, is the same placement decision seen from another angle: whether a response is assembled by the server, one resource at a time, or by the client, one query at a time.
References
Roy T. Fielding and Richard N. Taylor. 2002. Principled design of the modern Web architecture. ACM Transactions on Internet Technology 2, 2 (2002), 115–150. ↩︎
Gleison Brito and Marco Tulio Valente. 2020. REST vs GraphQL: A controlled experiment. In 2020 IEEE International Conference on Software Architecture (ICSA), 2020. 81–91. ↩︎
Olaf Hartig and Jorge Pérez. 2018. Semantics and complexity of GraphQL. In Proceedings of the 2018 World Wide Web Conference (WWW ‘18), 2018. 1155–1164. ↩︎