GraphQL vs REST: differences, trade-offs and when to use each
GraphQL vs REST: endpoints, response shape, caching, versioning and errors compared, with a tested Node example. GraphQL spec edition: September 2025.
GraphQL vs REST is a choice between two ways to design an API. REST exposes many URLs, each returning a fixed representation of a resource. GraphQL, a query language created in 2012 and open sourced by Facebook in 2015, exposes a typed schema through usually one endpoint, and the client asks for exactly the fields it needs. GitHub offers both APIs and says developers do not need to use one exclusively.
Last updated: 11 November 2026.
What is the difference between GraphQL and REST?
REST organises an API around resources with their own URLs, while GraphQL organises it around a schema that clients query. The table compares the two on the points developers ask about most, using the GraphQL project's documentation, Roy Fielding's dissertation, HTTP standards and GitHub's comparison page.
| Aspect | REST | GraphQL |
|---|---|---|
| What it is | An architectural style from Roy Fielding's 2000 dissertation; "REST API" is often used loosely for any HTTP API | A query language for your API and a server-side runtime that executes queries using a type system you define |
| Endpoints | One URL per resource, and the URL identifies the resource | A single URL, usually /graphql; entities are not identified by URLs |
| Response shape | Decided by the server; GitHub says its REST API returns more data than requested, in a pre-determined structure | Follows the query; GitHub says the GraphQL API returns exactly the data requested |
| Requests for related data | Several requests may be needed; GitHub's example takes 11 REST requests against 1 GraphQL request | One request can reach related data |
| HTTP methods | GET, POST, PUT, DELETE and others with defined meanings | POST must be supported; GET is optional and for queries only |
| HTTP caching | GET responses are cacheable, and the URL identifies the resource | POST responses are cacheable only with explicit freshness information; graphql.org recommends an id field so clients can build caches |
| Versioning | Often in the URL path or a parameter, as in Google's and Microsoft's API guidelines | The project advises evolving the schema instead of versioning, though nothing prevents a versioned service |
| Errors | An HTTP status code for each response | A response can hold both data and errors; the status is usually 2xx when data is present and 400 for validation errors |
| Abuse limits | A rate limit of its own (GitHub's REST API has a separate primary rate limit) | Cost-based limits (GitHub's GraphQL API assigns points to each query) and depth limiting |
No row in the table says one style is better. Each row names a different place where cost or complexity sits.
What is GraphQL, and who created it?
GraphQL is a query language for APIs and a server-side runtime for executing queries using a type system you define for your data. The GraphQL project says the language was created in 2012 and open sourced by Facebook in 2015, and that GraphQL is not tied to any specific database or storage engine.
The GraphQL Foundation now stewards it. According to graphql.org, Facebook and others created the foundation in 2019 as a neutral, non-profit home for GraphQL assets, hosted by The Linux Foundation. The specification site lists the September 2025 edition, published on 3 September 2025, as the latest release, and a working draft dated 28 September 2026 that is not a release. The JavaScript reference implementation, the npm package graphql (GraphQL.js), is at version 17.1.0 and MIT licensed.
What is REST?
REST stands for Representational State Transfer, an architectural style introduced in Roy Fielding's doctoral dissertation at the University of California, Irvine, in 2000. Fielding describes components that exchange representations of resources under constraints such as client-server, stateless requests, caching, a uniform interface and layered systems.
In daily use, MDN says, HTTP APIs are colloquially called RESTful although they do not necessarily follow every REST constraint. A typical REST API maps HTTP methods and status codes onto resources, as covered in the REST API guide.
What does the same request look like in REST and in GraphQL?
The same request takes two REST calls and one GraphQL query when a screen shows a user's name and the items in their orders. The sample below is hypothetical and tiny, with one user and two orders. REST routes return every field of each record, and one GraphQL endpoint returns only the fields in the query. It was run with Node 24.15.0 and the graphql package, version 17.1.0.
The data and schema come first. The resolver for user returns the record plus an orders field, which throws for any user that has no orders entry, to simulate a failing back-end service:
import { buildSchema } from "graphql";
export const users = {
1: { id: 1, name: "Asha", email: "asha@example.com", city: "Pune", plan: "pro", joined: "2026-01-12" },
2: { id: 2, name: "Ravi", email: "ravi@example.com", city: "Jaipur", plan: "free", joined: "2026-03-09" } };
export const orders = { 1: [
{ id: 101, item: "Notebook", amount: 450, status: "delivered", placedAt: "2026-10-01", courier: "BlueDart" },
{ id: 102, item: "Pen set", amount: 120, status: "shipped", placedAt: "2026-11-02", courier: "Delhivery" } ] };
export const schema = buildSchema(`
type Order { id: ID!, item: String!, amount: Int!, status: String! }
type User { id: ID!, name: String!, orders: [Order!]! }
type Query { user(id: ID!): User }`);
export const rootValue = {
user: ({ id }) => ({ ...users[id], orders: () => {
if (!orders[id]) throw new Error("orders service unavailable");
return orders[id]; } }) };The server file, saved next to the data file, offers two REST routes and one GraphQL endpoint. It sets the HTTP status following the graphql.org guidance: 200 when the result has data, 400 when it does not.
import http from "node:http";
import { graphql } from "graphql";
import { users, orders, schema, rootValue } from "./data.mjs";
http.createServer(async (req, res) => {
res.setHeader("Content-Type", "application/json");
if (req.method === "GET" && req.url === "/users/1") return res.end(JSON.stringify(users[1]));
if (req.method === "GET" && req.url === "/users/1/orders") return res.end(JSON.stringify(orders[1]));
if (req.method === "POST" && req.url === "/graphql") {
let body = ""; for await (const chunk of req) body += chunk;
const result = await graphql({ schema, source: JSON.parse(body).query, rootValue });
res.statusCode = result.data ? 200 : 400;
return res.end(JSON.stringify(result));
}
res.statusCode = 404; res.end("{}");
}).listen(4000, () => console.log("listening"));The client makes the two REST calls, then one GraphQL query, then two queries that fail in different ways:
const base = "http://127.0.0.1:4000";
const size = (text) => Buffer.byteLength(text);
const gql = (query) => fetch(base + "/graphql", { method: "POST",
headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query }) });
const user = await (await fetch(base + "/users/1")).text();
const orders = await (await fetch(base + "/users/1/orders")).text();
console.log("REST: 2 requests,", size(user) + size(orders), "bytes");
const ok = await gql("{ user(id: 1) { name orders { item status } } }");
const text = await ok.text();
console.log("GraphQL: 1 request,", size(text), "bytes, HTTP", ok.status);
console.log(text);
for (const query of ["{ user(id: 2) { name orders { item } } }", "{ user(id: 1) { phone } }"]) {
const bad = await gql(query);
console.log("HTTP", bad.status, await bad.text());
}Output from starting the server and running the client:
REST: 2 requests, 313 bytes
GraphQL: 1 request, 123 bytes, HTTP 200
{"data":{"user":{"name":"Asha","orders":[{"item":"Notebook","status":"delivered"},{"item":"Pen set","status":"shipped"}]}}}
HTTP 200 {"errors":[{"message":"orders service unavailable","locations":[{"line":1,"column":22}],"path":["user","orders"]}],"data":{"user":null}}
HTTP 400 {"errors":[{"message":"Cannot query field \"phone\" on type \"User\".","locations":[{"line":1,"column":17}]}]}The first two lines show the over-fetching point on this toy data: the two REST responses carried 313 bytes because they returned every field, and the one GraphQL response carried 123 bytes because the query named only name, item and status. That is a property of this example and not a general result. A REST API can also be designed with narrower endpoints, and no speed claim here rests on a published study.
How do GraphQL errors, caching and versioning work?
GraphQL errors, caching and versioning each work differently from REST. The fourth line of output shows a query for user 2 that returned HTTP 200 with data and an errors list at the same time. The orders field is declared non-null, so when it failed the null moved up to the user field. The GraphQL specification calls this an execution error and says it results in partial response data, with the error's path saying where it happened.
The fifth line shows a query for a field that does not exist, which fails validation and returns no data, so the server sent 400. The status code is the HTTP layer's choice, and graphql.org explains that HTTP has no status code for partial success.
Caching works differently. In a REST API the URL identifies a resource, so HTTP caches and clients can key on it, and MDN lists GET and HEAD responses as cacheable. In GraphQL there is no URL for an object, and graphql.org recommends exposing a globally unique id field so clients can build their own caches.
A GraphQL server may also accept GET for queries, and GET responses are the cacheable kind in HTTP. graphql.org's security guidance adds that when HTTP caching is used, sensitive data should be cached privately or not at all.
Versioning also differs. Google's AIP-185 requires Google APIs to carry a major version number, placed first in the URI path for REST APIs, and Microsoft's Azure guidelines use an api-version query parameter. The GraphQL project takes a strong opinion in favour of avoiding versions by letting a schema evolve, while stating that nothing stops a service from being versioned.
What are the costs and risks of GraphQL?
GraphQL shifts some of the work to the server. graphql.org describes the N+1 problem: a naive service can be chatty because each field has its own resolver, so one query for a list triggers many back-end requests. The usual fix is batching, for example with Facebook's DataLoader.
Open-ended queries also need limits. The GraphQL security guidance names depth limiting and demand control as protections against queries that nest deeply, and recommends HTTPS and request timeouts as for any HTTP API. GitHub's documentation shows one practical form: its GraphQL API assigns points to each query and limits the points usable in a time window, while its REST API has a separate rate limit.
REST has costs of its own, which the GitHub comparison page states: the REST API can return more data than requested and can need several requests for related data. REST is also familiar. GitHub notes that many developers already know HTTP verbs and concepts, and its REST call to create an issue is a single POST with a JSON body, whereas the GraphQL version first needs the repository's node ID.
Which should you choose: GraphQL or REST?
The choice between GraphQL and REST depends on who consumes the API and how varied their needs are. The lines below match common situations.
- A public or internal API with simple resources, mostly create, read, update and delete: REST keeps URL-based caching and the familiar HTTP verbs and status codes.
- A mobile or front-end team that needs different shapes of related data on each screen: GraphQL lets each client ask for its own fields in one request, which is the case GitHub gives for mobile applications.
- A small team that does not want to operate query-cost limits and batching: REST avoids that work, because those concerns come with GraphQL's open-ended queries.
- A product that already has a REST API and one screen that needs related data from many resources: add a GraphQL endpoint for that case, as GitHub does by offering both APIs.
- Learning purposes: build the REST API first, because HTTP methods and status codes also underlie GraphQL over HTTP.
For testing either style from a client, see the list of API testing tools. Teams splitting a system into services can also weigh the trade-offs in microservices vs monolith.
Is GraphQL replacing REST?
GraphQL is not replacing REST: the two styles coexist. GitHub, a large API provider, keeps both a REST API and a GraphQL API and says to use whichever best aligns with your needs, noting that node IDs let developers move between them. The GraphQL project itself documents GraphQL served over HTTP, the protocol REST APIs use. Each project decides by the consumers it has.
Where does API design fit in a forward deployed engineer path?
The Forward Deployed Engineer (FDE) Cohort is a Leap by Masai programme for engineers with hands-on technical experience, run as an in-person start in Goa, an online build phase and an in-person hackathon in Bangalore for qualifying participants, covering production Python, APIs, cloud deployment, LLMs, retrieval, agents, security and client engagement. Its online build phase lists APIs, webhooks, a versioned API in FastAPI, OAuth and role-based access.
Frequently asked questions
What is the difference between GraphQL and REST?
REST exposes many URLs, each returning a fixed representation of a resource, while GraphQL exposes a typed schema, usually at one endpoint, and the client states which fields it wants. GitHub's documentation says the GraphQL API returns exactly the data requested and the REST API returns more data than requested in a pre-determined structure.
Is GraphQL replacing REST?
No, GraphQL is not replacing REST. GitHub offers both a REST API and a GraphQL API and says developers do not need to use one exclusively. GraphQL is served over HTTP, and REST remains common for resource-style APIs. The two coexist, and the choice depends on the consumers of the API and the cost of running each style.
Does GraphQL work with HTTP caching?
GraphQL works with HTTP caching only partly. A GraphQL server must handle POST for queries and mutations and may also accept GET for queries, and MDN says POST responses are cacheable only with explicit freshness information. GraphQL has no URL for each object, so graphql.org recommends exposing a unique id field so that clients can build their own caches.
Who created GraphQL?
GraphQL was created in 2012 and open sourced by Facebook in 2015, according to graphql.org. In 2019 Facebook and others created the GraphQL Foundation as a neutral non-profit home for GraphQL assets, hosted by The Linux Foundation. The latest specification release on 11 November 2026 is the September 2025 edition.
Is GraphQL better than REST?
Neither is better on every measure. GraphQL fits clients that need different shapes of related data in one request, and REST fits resource-style APIs that benefit from URL-based HTTP caching and familiar methods. GraphQL adds work on the server, such as batching and query-cost limits. No performance claim holds without a named study of the workload.
Does GraphQL return an error status code?
A GraphQL response does not always carry an error status code. If a GraphQL response contains data, the server should respond with a 2xx status code even when the response also lists errors, because HTTP has no status for partial success, according to graphql.org. For validation errors that prevent execution, the server will typically send a 400 status code.
Next step
Run the three files above, add a field to the schema and to one REST route, and compare what each style asks of the client. For a programme that works through APIs, deployment and client engagement, see the Forward Deployed Engineer (FDE) Cohort page.