Key Takeaways
- REST is lower-friction for a broad third-party ecosystem.
- GraphQL becomes a better trade when several first-party clients need different portions of the same relational data.
- The case for federation grows as more teams contribute to the schema.
- Performance research will not decide between REST and GraphQL.
- Three peer-reviewed latency studies comparing the two were published between 2019 and 2025, alongside an earlier peer-reviewed study of payload size. Each tested a different workload.
Start with these questions instead: Who consumes your API? How relational does an average view typically need to be? How many teams would need to change your schema? That last question can carry the largest organizational bill, which is why Netflix adopted federation after its aggregation layer became a monolith maintained by a team outside relevant domains. GraphQL documentation makes the price clear by calling for a dedicated gateway and schema registry team. Test for a cheaper option before paying a larger bill. Where clients’ only complaint is receiving fields they discard, OData and JSON:API (two standardized conventions layered on top of REST) add field selection and related-entity fetching inside a stack using REST without introducing a GraphQL runtime. OData also carries ratifications from OASIS and ISO/IEC. gRPC handles calls between your own services, and tRPC covers a stack written end-to-end using TypeScript.
Treat the public-versus-internal split as a starting point. Shopify made its REST Admin API a legacy API in Oct 2024. GitHub has exposed GraphQL since 2016 but has a point budget with per-query costs. Both companies have absorbed those operating expenses intentionally.
| Dimension | What It Means for Your Project |
|---|---|
| Adoption | Your team’s 93% REST adoption rate likely reflects familiarity, not a deliberate REST-vs-GraphQL evaluation. |
| Performance | Peer reviewed articles on GraphQL vs REST performance from 2019 through 2025 all measured different workloads, and none established a clear winner across the board. |
| Query Semantics | If over-fetching is your only complaint, OData’s $select solves it without requiring a GraphQL gateway or schema registry. |
| Caching | Your CDN can cache GraphQL only after you add persisted documents or APQ so the query becomes part of the GET URL. |
| Security | IBM researchers found an exploitable gap in GitHub’s query cost model roughly three years after it launched so your team must treat cost analysis as ongoing work. |
| Organization | Federation shifts schema ownership to domain teams but requires a dedicated platform team to run the gateway and registry. |
The Short Answer
Start with what you need from the consumer perspective, and if another solution exists that gets you closer to that goal without paying a premium, go with that option.
A broad ecosystem of third-party solutions commonly favors REST. Several first-party client applications with divergent data requirements needing different views of the same relational graph make GraphQL worth considering. Multiple teams coordinating on one shared schema favor federation. Finally, for internal calls within your company, gRPC is commonly preferred.
If your only concern with REST is over-fetching, receiving more data than the client needs, consider OData or JSON:API first. Both let the client fetch data by stating exactly what it needs. Shopify, GitHub, and Netflix each use more than one of these at different layers.
Defining REST and GraphQL
Roy Fielding described REST in his 2000 doctoral dissertation at the University of California, Irvine. It is an architectural style and not a communication protocol. Resources have URLs, clients interact with resources applying standard HTTP methods (e.g., GET, POST, PUT, DELETE) to read and manipulate data, and servers define what the representations returned to clients should look like.
Request /users/123 and the server returns its definition of a user, display name, and billing status alike. The client then chooses which parts of this information they wish to keep. The rest is unnecessary data transfer, a classic example of over-fetching.
Meta developed GraphQL internally starting in 2012. Meta released it publicly on September 14, 2015. The GraphQL query language is paired with a runtime that executes queries over a type-checked schema. In 2018, the Linux Foundation announced it would create a neutral GraphQL Foundation. The Foundation and the GraphQL Specification Project were both established in 2019. The September 2025 edition is the first full release of the specification since October 2021.
GraphQL clients send queries to the server requesting specific fields. The GraphQL server returns JSON shaped like that query, carrying only the data the client asked for. Conventionally, a single endpoint (/graphql) is used for all operations, in place of REST’s multiple endpoints.
One common microservices association needs to be corrected here. As stated by the GraphQL Foundation, Meta has used a monolithic GraphQL API since 2012. The microservices use case came afterward.
REST vs GraphQL Across Ten Decision Factors
Each decision criterion does not independently decide whether you choose REST or GraphQL. The table sets out the key differences in the GraphQL vs REST debate and where each approach falls within your own organization.
| Decision Criterion | REST | GraphQL |
|---|---|---|
| Data fetching | The server decides the data included in a response, unless you implement OData or JSON:API to allow field selection. | The client specifies the precise data fields to include in the response, which carries exactly the data requested. |
| Performance | Often lower overhead per request on flat payloads, and some benchmarks show better scalability under concurrent load. | Often fewer round-trips occur on relational reads, and a single request can retrieve relational data across multiple resource types. Resolver execution and parsing require CPU usage on the server. |
| Caching | GET resource URLs fit standard HTTP and CDN caching naturally, provided standard cache headers are present. | Queries may use GET; persisted documents make bounded stable cache keys practical. POST is still common. |
| Versioning | Common practice: URI or media-type versioning. Multiple versions accumulate unless older routes are removed. | Common practice: additive schema evolution plus @deprecated, so APIs evolve without breaking existing queries. If telemetry shows there is no activity in relation to a given field, remove it. |
| Flexibility | New client requirements usually mean changes to multiple endpoints on the backend API. | Teams building front-end can modify queries to match their own data requirements without having to open tickets for backends. |
| Complexity | Low floor. When there is N+1 it manifests as network roundtrips. | Comparatively higher floor. When there is N+1 it manifests as database queries. |
| Security | Request shape constraints are more typical for each endpoint, and HTTP status codes communicate error conditions in a way that standard tooling and gateways already understand. Standard gateway rate limiting applies. | Client requests determine depth and breadth. Explicit demand controls usually needed: depth/breadth limits, cost analysis, or trusted documents. |
| Tooling | Mature codegen, universal HTTP tooling for API development. OpenAPI. | GraphQL Codegen, per-operation types, introspection-driven API documentation, schema ships inside server. |
| Learning curve | Anyone familiar with HTTP can start today. | Need to learn: GraphQL schema definition language, resolvers, batching, cost controls. |
| Best fit | Public APIs, read heavy content, service-to-service calls, flat CRUD. | Backend for frontend, mobile clients, relational dashboards, multi-team graphs. |
What the GraphQL vs REST Performance Research Found
Brito et al. (SANER 2019) are often quoted when discussing how much smaller the returned JSON payload is in GraphQL. They reported that moving seven open-source clients of the GitHub and arXiv REST APIs to GraphQL resulted in 94% fewer fields and 99% fewer bytes (median).
Be sure to reference the methodology before referencing the numbers. Those numbers are measuring what clients received and discarded from the server. Brito et al. report no significant decrease in the number of API calls made, clients still issued multiple requests, and do not measure latency.
Three subsequent studies measured latency and reached different conclusions. Seabra, Nazário, and Pinto at SBCARS 2019 migrated three applications and found GraphQL faster in two, with REST ahead above roughly 3,000 requests. Jin, Cordingly, Zhao, and Lloyd at IEEE IC2E 2025 compared REST vs. two GraphQL implementations using an Amazon Aurora DB holding the CMS Open Payments dataset. Apollo Server, self-hosted, had average round-trip times that were 25%-67% lower than REST on most endpoints; however, AppSync (also from AWS), a managed service, was slower than REST throughout. However, REST scaled better than AppSync and Apollo, with round-trip times 65%-72% lower than AppSync and Apollo once there were more than 80 concurrent threads (the authors also noted that the Apollo instance used in the experiment could not scale horizontally). Elghazal, Aneiba, and Shahra at WEBIST 2025 tested REST at roughly 3.7 times faster in a Go microservices setup, while GraphQL sent significantly less data overall.
Each study measured a different workload, and each study produced a different performance profile when comparing GraphQL and REST. That variance is itself the result. Variance is lost when studies’ results are averaged together.
Vendor-published studies also have issues because vendors choose the workload they publish. Be sure any referenced number is consistent with your traffic before accepting it. For example, if you are publishing a flat CRUD benchmark, it will provide little insight regarding performance of a relational dashboard.
The N+1 Problem in REST and GraphQL
N+1 describes a system where one query is executed to retrieve a list, followed by another query for each item within the list. That is under-fetching, the REST form of the problem most commonly associated with GraphQL discussions around over- and under-fetching. The first response does not carry enough, so the client goes back for more, turning one read into multiple API calls. For example, 25 users and their posts result in 26 operations instead of two.
GraphQL can exhibit the N+1 problem at the database level. GraphQL.js resolvers execute independently and do not coordinate with other resolvers, nor do they automatically batch related loads. As a result, a naive resolver implementation will issue 26 separate database queries. DataLoader, maintained under the GraphQL Foundation, aggregates related loads into one call to your batch function, which retrieves data for every key at once. In cases such as our sample example of 25 users and their posts, where all required IDs may be fetched in a single query, this reduces the number of backend fetches from 26 down to two.
REST can display the same N+1 problem but at the network layer. If an API exposes posts only as sub-resources, and the client iterates through the returned list to fetch the posts, then calling /users once and /users/:id/posts 25 times to fetch the posts for all the users generates 26 HTTP round trips across multiple requests. As an added layer of complexity, this process requires network and serialization overhead for each item. How this compares to the database work depends on topology and query cost. Both REST and GraphQL patterns fall under the Chatty I/O antipattern in Microsoft’s Azure Architecture Center. The ORM version is called N+1, and issuing a single logical operation as multiple requests is the same failure.
To reduce the REST version of N+1, use a batch endpoint. This can fragment cache keys, since ?ids=1,2,3 and ?ids=3,1,2 produce different cache keys at a CDN, and complicate invalidation. The CDN will not know which URLs contain a specific ID unless you tag yourself.
A fair GraphQL vs REST comparison would compare optimized REST versus optimized GraphQL or batched REST versus DataLoader. Comparing an optimized REST solution to an unoptimized GraphQL solution measures how well your optimizations worked rather than comparing solutions themselves.
JSON:API and OData Add Query Semantics to REST
REST endpoints, when returning a static output, force clients to discard fields they didn’t ask for. Although these two specifications don’t move from REST to GraphQL, they eliminate a restriction that traditional REST APIs leave in place. They address disagreements over response formats and pagination conventions so clients and tooling can be built once against a single defined contract.
JSON:API describes how a client may request a resource and how a server will shape the response back to the client, including the data structures within it; it also states that its goal is efficient data fetching, minimizing the total number of HTTP requests and the amount of data sent over the wire. Version 1.1 was finalized on Sept 30th, 2022. A client selects fields to return with the fields[articles]=title,body parameter and includes multiple related resources with include=comments.author, which returns the requested data in a single request. This method is a standardized approach to data fetching for REST services, avoiding N+1 queries without inventing separate syntax for each service.
OData has similar goals but goes far beyond them, covering best practices both for teams that create APIs and for the clients consuming them. OData defines conventions to exchange data, as well as conventions for constructing URLs, payloads, and a machine-readable metadata document that describes the structure of your data model, allowing generic clients and tooling to interact with your service regardless of whether you create custom code for it. Beyond selecting fields with $select and expanding relationships into a single response with $expand, clients can also filter individual rows with $filter, order results with $orderby, skip items with $skip, limit returned items with $top, and finally calculate derived values with $compute.
The difference in recognition matters when procurement or compliance requirements ask what an API adheres to. JSON:API is a published, versioned specification whose media type has been registered with IANA, and is maintained by its authors. OData has received external validation by formal ratification processes. Specifically, OData version 4.01 was approved as an OASIS Standard in June 2020, the organization’s most stringent designation, and OData version 4.0 was officially adopted as ISO/IEC 20802 for open data exchange, with Part 1 of that document released in Dec. 2016 and OASIS announcing the adoption in Feb. 2017.
Microsoft and SAP run OData in production. The quote from Alex Simons in the OASIS announcement states that “Microsoft Graph uses OData to provide a single unified API across Microsoft 365”, which includes Teams, Outlook, and what is currently Microsoft Entra ID, along with other services. SAP’s Alexander Lingg wrote OData in the same announcement as being the underlying technology powering SAP’s user experience.
However, neither solution is free, and none fully resolves the issue. For example, when you need to represent complex data with deeply nested selections, it can be easier to write your selection as a GraphQL query than to write a URL for an OData call. Federation provides another capability: it allows a developer to compose multiple source schemas into one graph and distribute schema ownership and execution across the sources.
If a development team adopts GraphQL solely to avoid over-fetching data, it may be buying a gateway, a schema registry, and a query security program to solve something $select already solves.
Caching Strategies and Security Costs
Unlike REST, where requests map to distinct resource URLs, GraphQL clients by convention send POST requests to a single endpoint. RFC 9111 requires both the request method and URI to be included in the minimum cache key, while standard HTTP caches typically don’t include POST data in their keys; this means standard caches generally cannot distinguish GraphQL POSTs by query body.
The GraphQL-over-HTTP specification allows sending GETs for query operations, effectively making the query part of the URI (and therefore part of the cache key).
Persisted documents make those URI-bound GETs short and bounded by swapping a long query string with a short identifier. Automatic persisted queries: APQ learns this mapping at runtime. The client sends a hash; if the server doesn’t recognize it, then the client tries again with the full operation and the server caches the two together. This approach requires no build step or registry.
Trusted documents are pre-approved persisted documents. They restrict execution to approved operations ahead of time. While this limits the attack surface, sending their persisted identifiers via GET can also enable CDN caching; however, it requires a registry or equivalent coordination at release time. It will only work where all operations are known ahead of time, which rules it out for an open API.
Both security and caching costs come from similar origins. For example, a client-defined query depth and breadth mean a small query may have elevated operational costs to serve. At ICSOC 2019, Wittern et al. studied real-world GraphQL APIs in An Empirical Study of GraphQL Schemas. They identified exponential worst-case behavior in over 80% of commercial and larger open-source schemas.
Because the GraphQL specification includes no query cost model, implementors must provide their own demand controls. GraphQL.org recommends depth limiting; cost analysis assigns field-based point values to each field (where expensive queries are rejected before execution).
The practical application of that gap is clear in GitHub. In A Principled Approach to GraphQL Query Cost Analysis, an IBM Research team surveyed 30 public GraphQL APIs in February 2020 and found 25 documenting no query cost analysis at all. The same team then identified a flaw in GitHub’s own analysis, issued an exponential query that GitHub accepted and executed until it timed out, and disclosed it. GitHub fixed it.
GitHub first introduced its GraphQL API at Universe in September 2016 and ended early access in 2017. Their cost analysis had therefore been running in production for roughly three years when the IBM researchers collected their data. Query cost analysis requires a named owner and a regular cadence for reviewing costs.
This does not make GraphQL uniquely exposed. OWASP has published cheat sheets covering GraphQL and REST that address issues related to input validation, access controls, and rate limiting. Each technology has its own ‘security budget’.
Where REST Stays the Lower-Friction Default
If you hand a REST API over to developers outside your organization, REST is generally less complicated. Most developers will be familiar with it; generating SDKs for nearly every programming language is easier, HTTP status codes map predictably to outcomes, and debugging via “curl” is possible. An open third-party API generally cannot rely on a trusted document allowlist because client operations are not known in advance; therefore, using GraphQL increases the cost of protecting against unauthorized queries, so you will need to rely upon depth limits and cost analysis. Both Shopify and GitHub chose to bear the cost; it is a cost, and one both companies chose to pay to enter.
Read-heavy content that is the same for every end-user should remain on REST. REST relies on the URL as its cache key. Serving a catalog directly from CDN edge servers means the content never reaches your origin server, and the financial benefits are compelling.
An antipattern occurs frequently enough to warrant naming. If your front end consumes data in a way similar to what your database already stores, adding a GraphQL layer between them provides no bandwidth savings. Instead, it adds risk from N+1 queries, creates caching challenges, and muddles monitoring tools that assume only one API endpoint per operation. Error handling differs as well. REST signals failure through HTTP status codes. GraphQL usually returns 200 with an errors array, and dashboards keyed on status codes go quiet.
GraphQL Suits Multi-Client Data Graphs
Backend for Frontend (BFF) is where GraphQL shines, and where clients carry complex data requirements. An iOS app, a web dashboard, and a partner portal each have different data requirements across the users, orders, and inventory information. Plain fixed-shape REST often gives each client an endpoint for its required data, or it overfetches. GraphQL gives each client one schema over multiple data sources.
Bandwidth-constrained clients benefit from the reason Meta built it. Packing multiple resources into one response removes the round-trip stacking that makes mobile apps feel slow on poor networks.
Federation composes several independently owned GraphQL schemas, called subgraphs, into one graph that clients query through a single router. Although there may be a technical case for using GraphQL for federating schemas across multiple teams in an organization, the organizational case can run stronger than the technical one. Each team can control its own subgraph and deploy it independently of other teams, while clients still see the same API through a single schema.
Implementing GraphQL federation comes with costs that should be understood before implementation. GraphQL has been very open about this in its documentation and specifically states that federation calls for substantial infrastructure support, including a dedicated team to operate the gateways and schema registries. As mentioned previously, the official documentation identifies this as personnel dollars. Additionally, the Composite Schemas specification is a draft and therefore, standardized portability between federation implementations remains unsettled.
RPC Frameworks for Internal and TypeScript Stacks
Remote Procedure Calls (RPC) predate both RESTful architecture and GraphQL, and shaped the SOAP web services that dominated enterprise integration before REST. Unlike REST, which models an API as a collection of resources which a client performs actions upon, RPC models an API as methods invoked remotely. To invoke an RPC function, a client appears to invoke a local method, and the RPC libraries handle serialization/deserialization and transport. Type-driven RPC systems (including those generated via IDL) specify that both parties agree on the set of available functions/method names along with arguments and return types ahead of time. Therefore, statically typed generated clients know each method name, argument, and return type at compile time. Not all RPC libraries work this way. JSON-RPC specifies no schema whatsoever.
Google created gRPC out of its internal Stubby system and released it as an open-source project in 2015. gRPC pairs Protocol Buffers with HTTP/2. Using Protocol Buffers, you describe your services in a .proto file and subsequently generate typed clients/servers for each language you wish to support. While designed for east-west communication (i.e., service-to-service communications internally within your network), gRPC excels at efficiently performing typed remote procedures and frequently results in lower serialization and transport overhead. Be sure to benchmark the actual workload you plan to execute. Google donated gRPC to the Cloud Native Computing Foundation (CNCF) in 2017.
However, gRPC does not natively run in browsers. Browser-based clients utilize gRPC-Web, which uses a proxy (such as Envoy) to translate between gRPC-Web and native gRPC. The payload remains as Protocol Buffer messages, while the browser side uses normal browser HTTP and the proxy translates to native gRPC.
tRPC solves a specific problem that is much more limited in scope than REST or GraphQL. If you can guarantee that both client-side and server-side implementations of your application will be written in TypeScript, and therefore able to reference shared type definitions, tRPC gives you end-to-end type checking without requiring a schema definition file or any code-generation steps. The server simply exposes its router type, and the client consumes it directly. Since this easily accomplished within a single repository, convenience does not imply dependency.
Regardless, since you are defining the bounds of your application within your TypeScript implementations, adding a Swift client or partner integration will no longer allow tRPC to cover all consumers; you will need another interface or contract to accompany it.
How Shopify, GitHub, and Netflix Decided
Shopify migrated its Admin API from REST to GraphQL. As of October 1st, 2024, the REST Admin API became a legacy API. This means that Shopify now points new applications to the GraphQL Admin API. Shopify claims they benefit from GraphQL due to the “calculated query-cost model” versus the request-based limits associated with REST. Shopify states another benefit of GraphQL is that it allows for exact field selection, the ability to retrieve related data, and typing. The legacy REST API on standard stores has a limit of 40 requests per bucket, replenished at a rate of two requests per second. Additionally, higher limits are available based on the store plan level chosen, and every API request counts regardless of whether it returns one field or a thousand fields. The GraphQL Admin API calculates cost against point budgets that also vary by plan, so an application that requests fewer points will consume fewer points.
GitHub maintained GraphQL public but established cost as the gateway. Their API assigns points to each query and caps users at 5,000 points per hour with a maximum of 500,000 nodes per call before execution. This model remains the reference model for exposing a graph to the open internet. The IBM disclosure above indicates that even after implementation, the model required maintenance.
Netflix used the move to shift how the company viewed ownership. The Studio API aggregation layer became a bottleneck because one team owned a schema across multiple domains they did not understand. By using federation, Netflix relocated schema ownership to the domain teams that understood the schemas. The migration method should receive as much consideration as the end result. Netflix documented replay testing against live traffic and compared old responses to new ones before cutting anything over.
AI Agents and the Model Context Protocol
According to Postman’s 2025 survey, 89% of developers report using generative AI in their daily work; however, 24% indicate they design APIs with AI Agents in mind. This creates a gap that needs to be addressed in the upcoming planning cycle.
Anthropic introduced the model context protocol in November 2024. It was subsequently donated to the Agentic AI Foundation on December 9, 2025 via the Linux Foundation. At the time, Anthropic said it had more than 10,000 active servers.
MCP sits above your API. The July 2026 specification transitioned the protocol into a stateless model, removing the initialization handshake and protocol-level sessions. Each request is now self-descriptive. A client can request a server’s capabilities; however, it is not required. The specification includes authorization, along with the tools/resources/prompts an agent may use. Typically, MCP servers provide a wrapper around APIs that an organization currently provides.
Therefore, the protocol decision is unchanged; however, the number of consumers the protocol must support has increased by one. Agents operate within a limited context window. A limited context window can favor compact machine-readable API descriptions or a curated tool surface over hundreds of independently described operations.
This disallows neither technology. Account for a third consumer beyond web and mobile, and determine who owns tool definitions before someone ships a version from their laptop.
Five Questions Before You Commit
Ask yourself these questions before the architecture review begins.
- Who consumes this API? External developers often favor REST. First-party clients you control open the door to GraphQL. Other services point to gRPC. Count agents as a consumer too.
- How relational is each view typically? Stronger candidates for GraphQL include screens assembled from multiple joined entity tables. Screens mapped directly to one table are not stronger GraphQL candidates.
- Could field selection alone solve this? If all complaints are about overfetching, then OData or JSON:API can resolve this issue without implementing a gateway.
- Are multiple teams responsible for this API? Rarely does one team require federation. Six teams queuing behind a single schema owner is the problem Netflix solved.
- Will you fund cost analysis of queries as ongoing work? Researchers at IBM identified a gap in GitHub’s GraphQL implementation roughly 3 years after it was made generally available.
These three companies have implemented a mix of API architectures rather than one common architecture. They have also used a variety of protocols. Shopify pushed its public Admin API toward GraphQL, GitHub has offered GraphQL publicly since 2016 alongside its existing REST API, and Netflix has federated an internal graph across services that communicate via REST and gRPC. What unites them is the intentional placement of each protocol with the understanding of the operating costs associated with each method. Sequencing the migration to the new architecture is the more difficult part of the work. Hiring custom software development services to test migrations ensures your engineers continue delivering new functionality throughout the transition.
Compare your workload’s performance, regardless of how you choose to implement it. Published benchmarks will never settle the debate.






