REST is the safe default for most APIs in 2026, and that is not a boring answer — it is the correct one. GraphQL solves specific, real problems around data-fetching flexibility, but those problems are not universal. Choosing GraphQL when REST would have worked is one of the more common engineering own-goals: you get a schema language, a resolver layer, and a new category of production problems in exchange for a problem that may not have existed.
What changed in 2026
- tRPC matured as a third option — for TypeScript full-stack apps (Next.js, Remix, T3 stack), tRPC's end-to-end type inference means you get the flexibility of custom endpoints with type safety that GraphQL schemas approximate but rarely match cleanly.
- GraphQL federation (Apollo, WunderGraph) stabilised — composing multiple GraphQL services into a supergraph is production-grade, which makes GraphQL more attractive for micro-service backends.
- REST tooling caught up on type safety — OpenAPI 3.1 + code generation (via Orval, Hey API, or openapi-typescript) gives REST roughly the same client-side type safety as GraphQL without the runtime overhead.
- HTTP caching for GraphQL improved — persisted queries and GET-based query execution mean edge caching is possible, though it is still more work than REST.
Core difference
REST maps operations to HTTP verbs and URLs:
GET /users/42 → fetch user
GET /users/42/posts → fetch user's posts
PATCH /users/42 → update user
DELETE /posts/99 → delete post
Each endpoint returns a fixed shape. Clients get exactly what the server decided to return — which may be too much (over-fetching) or require multiple calls (under-fetching).
GraphQL has one endpoint. The client specifies exactly what it needs:
query {
user(id: "42") {
name
avatar
posts(first: 5) {
title
publishedAt
}
}
}
The response shape exactly mirrors the query. One request, arbitrary depth, no over-fetching.
Comparison table
| Dimension |
REST |
GraphQL |
| Learning curve |
Low |
Moderate–High |
| HTTP caching |
Native (GET is cacheable) |
Requires persisted queries / CDN config |
| Over-fetching |
Common (fixed response) |
Eliminated (client-specified fields) |
| Under-fetching |
Common (multiple requests) |
Eliminated (nested queries) |
| Type safety |
Via OpenAPI + codegen |
Via schema introspection + codegen |
| N+1 problem |
No (server controls queries) |
Present (DataLoader required) |
| File uploads |
Simple multipart |
Awkward (spec is non-standard) |
| Subscriptions |
SSE or WebSocket (ad hoc) |
First-class subscription type |
| Versioning |
Via URL (/v2/) or headers |
Schema evolution (non-breaking additions) |
| Operational tooling |
Mature (cURL, Postman, etc.) |
Growing (Apollo Studio, GraphiQL) |
The N+1 problem
This is the most common production GraphQL mistake:
// Naive resolver: N+1 queries
const resolvers = {
Query: {
posts: () => db.posts.findAll(), // 1 query
},
Post: {
author: (post) => db.users.findById(post.authorId), // N queries — one per post
},
};
// Correct: DataLoader batches and deduplicates
const userLoader = new DataLoader(async (ids) => {
const users = await db.users.findByIds(ids);
return ids.map(id => users.find(u => u.id === id));
});
const resolvers = {
Post: {
author: (post) => userLoader.load(post.authorId), // batched: 1 query total
},
};
Every GraphQL API with nested relationships needs DataLoader (or an equivalent per-request batch loader). Without it, a query for 100 posts fetches 100 author records individually.
How to pick
- Simple CRUD API for a single client type? → REST. Simpler, easier to cache, less to learn.
- Multiple clients (mobile + web + third-party) with different data needs? → GraphQL.
- TypeScript full-stack monorepo (Next.js, SvelteKit)? → tRPC. Better DX than both REST and GraphQL for the same-codebase scenario.
- Micro-service backend needing a unified API layer? → GraphQL federation (Apollo Router or WunderGraph).
- API consumed by third parties (public API)? → REST. OpenAPI docs and cURL compatibility reduce the barrier for external developers.
- Real-time subscriptions as a first-class feature? → GraphQL subscriptions or a purpose-built WebSocket protocol.
Common mistakes
Not rate-limiting query complexity. A client can write an arbitrarily deep GraphQL query that joins thousands of records. Use graphql-depth-limit and a query cost analyser in production:
import depthLimit from 'graphql-depth-limit';
import costAnalysis from 'graphql-cost-analysis';
const server = new ApolloServer({
validationRules: [
depthLimit(7),
costAnalysis({ maximumCost: 1000 }),
],
});
Exposing your database schema directly as a GraphQL schema. This makes the API brittle to database changes and often over-exposes internal fields. Design the GraphQL schema for clients, not for the database.
Treating GraphQL mutation errors as HTTP 200 with an error array. Decide upfront whether errors are thrown (non-nullable fields) or returned in the response shape. Mixed approaches confuse client developers.
What to skip
- GraphQL for internal microservice-to-microservice calls — gRPC or plain REST is simpler and faster.
- REST versioning via URL for purely additive changes — use field additions and deprecation headers instead.
- Apollo Client for simple read-heavy apps — React Query (TanStack Query) + REST is far simpler when you do not need the full GraphQL client cache.
FAQ
Is GraphQL faster than REST?
Not inherently. GraphQL reduces round trips (fewer requests), but resolver overhead and N+1 risks can make it slower than a well-designed REST endpoint. Benchmark your specific queries.
Can I mix REST and GraphQL in the same app?
Yes — many teams use REST for public APIs and file uploads, GraphQL for their main client-facing data layer, and tRPC for admin tooling. Pick the right tool per use case.
Does GraphQL replace OpenAPI?
No — they serve different purposes. GraphQL is a query language; OpenAPI is a documentation and code-generation standard. You can generate OpenAPI specs from a GraphQL schema, but they are complementary.
What is the best GraphQL server library in 2026?
For Node.js: Pothos (schema-first with TypeScript) or Yoga (runtime-agnostic, Cloudflare Workers compatible). For Go: gqlgen. For Python: Strawberry. Apollo Server remains popular but is no longer the clear default.
Where to go next