REST vs GraphQL: Key Differences, Examples and How to Choose
REST and GraphQL solve the same problem in very different ways. We build one screen with both, then compare data fetching, caching, errors, versioning and security, so you can pick with confidence.
- REST exposes many URLs, one per resource, and the server decides each response’s shape.
- GraphQL usually exposes one endpoint, and the client asks for exactly the fields it needs.
- REST gets HTTP caching and simple operations almost for free. GraphQL gives clients flexibility but moves complexity to the server.
- Choose REST as the default for simple or public APIs, and GraphQL when many clients need different data from connected sources.
The REST vs GraphQL debate is often framed as old versus new, but that misses the point. Both are good ways to move data between a client and a server, and many companies run both. The real question is which set of trade-offs suits your application and your team.
In this guide we build one small screen with each approach, then compare what actually changes in day-to-day work. The examples show the shape of requests and responses for a made-up blog API, rather than a specific server you can call.
The short answer
REST is an architectural style built on HTTP. Each resource has its own URL, and you act on it with standard methods such as GET, POST, PUT and DELETE. It has no single formal specification for how an API must look, though conventions and tools such as OpenAPI are widely used.
GraphQL is a query language and runtime for APIs, created at Facebook and open sourced in 2015. The server publishes a typed schema of everything available. Clients send a query naming the exact fields they want, and the response mirrors that query.
REST vs GraphQL side by side
| Aspect | REST | GraphQL |
|---|---|---|
| Endpoints | Many, one per resource | Usually one, such as /graphql |
| Who shapes the response | Server | Client |
| Over-fetching | Common | Avoided by design |
| Round trips for related data | Often several | Often one |
| Contract | Optional, such as OpenAPI | Required, typed schema |
| HTTP caching | Works naturally | Needs extra work |
| Error signal | HTTP status codes | Often an errors array with status 200 |
| Changes over time | Often new versions | Add and deprecate fields |
| Learning curve | Gentle | Steeper, especially on the server |
One screen, two APIs
Imagine a blog author’s page that shows the author’s name, plus the titles of their three latest posts and the comment count for each.
With REST
A typical REST design needs several requests:
GET /authors/42
GET /authors/42/posts?limit=3
GET /posts/101/comments
GET /posts/102/comments
GET /posts/103/comments
That is five requests, and the first probably returns far more than the name, such as a biography, email and join date. You could reduce this by adding a comment_count field or an endpoint tailored to this screen. Real REST APIs often do exactly that, and it works well. The cost is that the server team must add something new for each new need.
With GraphQL
The same screen takes one request, sent as a POST to /graphql:
query AuthorPage {
author(id: 42) {
name
posts(limit: 3) {
title
commentCount
}
}
}
The response has the same shape as the query, and nothing extra:
{
"data": {
"author": {
"name": "Asha Rao",
"posts": [
{ "title": "Indexes in practice", "commentCount": 12 },
{ "title": "Reading query plans", "commentCount": 4 },
{ "title": "When to denormalise", "commentCount": 9 }
]
}
}
}
If the mobile app later needs each post’s date as well, it adds publishedAt to its query. No backend change is required, provided the field is already in the schema.
Over-fetching and under-fetching
These two terms explain most of GraphQL’s appeal. Over-fetching means receiving data you do not use, which wastes bandwidth, particularly on slow mobile networks. Under-fetching means one response is not enough, so the client has to make more requests.
REST can tackle both with sparse fieldsets such as ?fields=name,title, embedded resources or backend-for-frontend services. These are valid solutions, but each is something you build and maintain yourself. GraphQL includes this flexibility as a core feature, so it helps most when several clients, such as web, iOS and Android, need different data from the same backend.
Schemas and contracts
Every GraphQL API has a schema, written in a language like this:
type Author {
id: ID!
name: String!
posts(limit: Int = 10): [Post!]!
}
type Post {
id: ID!
title: String!
commentCount: Int!
}
type Query {
author(id: ID!): Author
}
The ! means a field cannot be null. Clients can also ask a server what it supports, known as introspection, which powers autocomplete in tools like GraphiQL and type generation for frontend code. On the server, small functions called resolvers fetch the data for each field.
REST can match much of this with an OpenAPI document, including generated clients and interactive documentation. The difference is that the REST contract is optional and can drift from the code, while a GraphQL server cannot run without its schema.
Caching
This is where REST has a clear advantage. Because each resource has its own URL and is fetched with GET, browsers, CDNs and proxies can cache responses using standard headers:
GET /posts/101
Cache-Control: public, max-age=300
ETag: "a1b2c3"
GraphQL queries usually travel as POST requests to one URL, so those same HTTP caches cannot tell them apart. Teams work around this with client-side caches, such as those in Apollo Client and urql, and with persisted queries, where a known query gets an ID and can be sent using GET. These work well, but they are additional pieces to set up.
Tip: If your API serves mostly public, read-heavy content such as a news site or product catalogue, weigh caching heavily. A CDN hit is often the cheapest and fastest response you can give.
Errors
REST uses HTTP status codes, so a missing post returns 404 and a bad token returns 401. Monitoring tools understand these automatically. Our HTTP status codes cheat sheet covers the ones you will meet most.
GraphQL servers commonly return status 200 even if part of the query fails, and put the details in an errors array next to any data that succeeded:
{
"data": { "author": { "name": "Asha Rao", "posts": null } },
"errors": [
{ "message": "Posts service unavailable", "path": ["author", "posts"] }
]
}
Partial success is genuinely useful, because the page can still display the author. But your client code must check for errors in every response, and dashboards that watch only status codes will miss these failures. Add monitoring that understands GraphQL.
Versioning
REST APIs often introduce breaking changes through a new version, such as /v2/posts, and then support both for a while. GraphQL encourages one evolving schema. You add new fields freely, since old clients simply do not request them, and mark outdated ones as deprecated:
type Post {
commentCount: Int!
comments_total: Int @deprecated(reason: "Use commentCount")
}
Field usage tracking then shows when it is safe to remove a deprecated field. Be aware that genuinely breaking changes are still possible, so this approach needs discipline too.
The N+1 problem and performance
GraphQL’s flexibility has a classic trap on the server. If the commentCount resolver runs one database query per post, then fetching 50 posts causes one query for the posts plus 50 for the counts. This is the N+1 problem.
The usual fix is batching with a tool such as DataLoader, which collects those lookups into a single query:
SELECT post_id, COUNT(*) FROM comments
WHERE post_id IN (101, 102, 103)
GROUP BY post_id;
REST endpoints can suffer from N+1 too, but because each endpoint’s query is fixed, you can tune it once. With GraphQL, clients can create query shapes you never planned for. Good database indexes matter with either approach.
Security
Authentication works similarly for both, typically with tokens in a header. The differences are elsewhere:
- Query cost: a client can send a deeply nested GraphQL query that is very expensive to resolve. Set depth limits, complexity limits and timeouts.
- Rate limiting: counting requests is simple in REST. In GraphQL, one request can do the work of hundreds, so limit by estimated query cost.
- Authorization: check permissions at the field or object level in GraphQL, since a single endpoint cannot be protected by URL rules alone.
- Introspection: consider disabling it for private production APIs, while remembering that this does not replace proper authorization.
How to choose
Choose REST when:
- You are building a simple CRUD service or a small team’s API.
- You are offering a public API to many unknown developers.
- HTTP caching is important to your performance or costs.
- You handle file uploads, downloads or webhooks heavily, since these fit plain HTTP well.
Choose GraphQL when:
- Several clients need very different views of the same data.
- Your screens combine deeply related data from many sources.
- Frontend teams change quickly and waiting on new endpoints slows them down.
- You can invest in server tooling, query limits and monitoring.
You do not have to choose only one. A common setup keeps REST services internally and adds a GraphQL gateway for the frontend. If you are unsure, start with REST, because it is simpler to run and easy to put a GraphQL layer on top of later. Whichever you pick, cover it with solid tests, as explained in our guide to unit vs integration testing.
Final thoughts
REST keeps the server in control and makes the most of HTTP. GraphQL hands control to the client and takes on more server complexity in return. Look at your real screens and consumers: count the round trips, check how often frontend work waits on backend changes, and let those numbers decide rather than trends.
Frequently asked questions
Is GraphQL better than REST?
Not in general. GraphQL suits clients with varied, connected data needs, while REST is simpler and better for HTTP caching. The right choice depends on your project.
Is GraphQL faster than REST?
Not automatically. It can reduce round trips and payload size, but a badly optimised resolver can be slower. A cached REST response is hard to beat.
Can GraphQL and REST be used together?
Yes, and it is common. A GraphQL gateway can sit in front of existing REST services, so frontend teams get one flexible API without a backend rewrite.
Does GraphQL replace a database?
No. GraphQL is an API layer and is not a database. Its resolvers can fetch data from databases, REST services or any other source.
Which should beginners learn first?
Learn REST first. It teaches HTTP methods, status codes and headers, which are useful everywhere and make GraphQL’s trade-offs easier to understand later.
Keep learning
Need help with testing, WordPress or SEO?
We help teams ship better software and grow their sites. Tell us what you are working on and we will suggest a clear next step.