REST exposes many URLs, one per resource, and the server decides what each response contains. GraphQL exposes a single endpoint, usually /graphql, where the client sends a query naming exactly the fields it wants. REST gets free HTTP caching by URL; GraphQL saves round trips and trims unused fields at the cost of more server work.
| Topic | REST | GraphQL |
|---|---|---|
| Endpoints | One URL per resource | One endpoint, often /graphql |
| Who picks the fields | The server | The client, in the query |
| Related data | Often several requests | Nested in one query |
| HTTP caching | Built in for GET by URL | Needs GET queries, persisted queries or a client cache |
| Errors | Mostly HTTP status codes | An errors array, often next to partial data |
| Schema | Optional (OpenAPI) | Required and typed |
Updated
The comparison below fetches the same screen of data both ways. Pick a scenario to see the REST calls a client would make next to the one GraphQL query that replaces them, and what each response leaves out or adds.
GET /api/users/42
GET /api/users/42/posts?limit=3query Profile {
user(id: "42") {
name
avatarUrl
posts(first: 3) { title }
}
}REST needs a second call for the posts, and both responses carry every field the server includes. The GraphQL query returns just the name, avatar and three post titles.
| Your situation | Leans toward |
|---|---|
| Public API that third parties call with curl | REST |
| Many screens, each needing different related data | GraphQL |
| Heavy CDN caching of read traffic | REST, or GraphQL with persisted GET queries |
| Mobile clients on slow networks | GraphQL, fewer round trips |
| Simple CRUD over a few tables | REST |
| Several backend services behind one front door | GraphQL as a gateway layer |
Every operation is a POST of JSON with query and variables keys to the one endpoint. The Accept header asks for the newer GraphQL response type, with plain JSON as a fallback.
const res = await fetch("/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/graphql-response+json, application/json",
},
body: JSON.stringify({
query: "query ($id: ID!) { user(id: $id) { name } }",
variables: { id: "42" },
}),
});
const { data, errors } = await res.json();
if (errors) console.warn(errors);One field failed, so it is null and the errors entry names it with path. The rest of the data still arrived.
{
"data": {
"user": { "name": "Ada", "posts": null }
},
"errors": [
{
"message": "Posts service timed out",
"locations": [{ "line": 1, "column": 30 }],
"path": ["user", "posts"]
}
]
}
A REST endpoint returns a fixed shape. A list screen that shows only names still downloads every field of every user, which is over-fetching. A detail screen that needs a user plus their posts plus each post's comment count may need several calls, which is under-fetching.
GraphQL moves that choice to the client. The query lists the fields, the server returns that shape and nothing else. The trade is that the server must handle any combination of fields a client might ask for.
With REST, a URL is a cache key. Browsers and CDNs can store a GET response and reuse it, because GET and HEAD are the cacheable methods. Most GraphQL traffic is POST to one URL, which those caches do not store.
GraphQL clients make up for it in the app: they split responses into objects and store them by id, so two queries that touch the same user share one cached record. graphql.org recommends a globally unique id field for exactly this reason.
REST leans on the status line: 404 for a missing record, 401 when not signed in, 422 for bad input. Clients can branch on the code alone.
A GraphQL response has a data key, an errors key, or both. A syntax or validation problem gives errors and no data. A failure inside one field gives partial data where that field is null, and an error entry whose path points at it.
Not by itself. It can cut round trips and payload size for screens that need related data, but each query may cost the server more to resolve. A well designed REST endpoint for one screen can be just as quick.
A field can fail while others succeed, so the server returns the data it has plus an errors array. The GraphQL over HTTP guidance uses 2xx for partial success; servers answering with application/graphql-response+json use 400 when a request fails validation.
Yes, if queries are sent with GET, which servers may allow for queries only. Long queries can exceed URL limits, so teams use persisted queries that send a short hash instead of the whole document.
Many teams do. File uploads, webhooks and simple public resources often stay as REST routes while app screens read through a GraphQL layer that may call those same services underneath.
No. A GraphQL server is a layer of resolvers, small functions that fetch each field from wherever the data lives: a database, a REST service or a file. The schema describes shapes, not storage.
Related: GraphQL mutations, GraphQL fragments, HTTP status codes, JavaScript async and fetch, Webhook vs API.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.