Whenever I start a new client project or evaluate an architecture for a team, I inevitably get asked the same question:
"Should we build our frontend using REST or GraphQL?"
A few months ago, I was architecting a dashboard feature that needed to display user profiles, their published articles, and nested comment threads.
The REST advocates pushed for standard endpoints (/users, /posts, /comments), while frontend engineers worried about waterfall network requests (GET /users/1, then GET /users/1/posts, then GET /posts/:id/comments). The GraphQL advocates wanted a single query, while the backend team worried about schema complexity.
When I wanted to build a quick side-by-side prototype to settle the debate with real code, I ran into an absurd roadblock: I couldn't find a single mock backend that supported both REST and GraphQL queries on the exact same underlying dataset with mutation support.
I had to either spend an entire weekend building two separate mock servers, or give up on an authentic side-by-side comparison.
That frustration was a huge driver behind adding a native GraphQL gateway to Playground API — allowing developers to query the exact same data via REST or GraphQL side-by-side with zero setup.
In this post, we're skipping the theoretical internet debates. I'll show you how to build the exact same React CRUD feature using both approaches against a unified, live backend sandbox.
The Scenario: A User Profile with Posts and Comments
Let's build a user profile screen that needs to display:
- User details (name, email, company)
- The user's published blog posts
- The latest comments on each post
Let's look at how REST and GraphQL approach this data requirement.
Approach 1: The REST Workflow
In standard REST architecture, resources are organized around distinct URL endpoints.
Fetching Data in REST:
To get the user and their related posts and comments, a REST client typically executes sequential or parallel HTTP GET requests:
1. GET /api/v1/users/1 ──► Fetches User profile
2. GET /api/v1/users/1/posts ──► Fetches User's posts
3. GET /api/v1/posts/10/comments ──► Fetches Comments for Post 10
The Advantage: Simple, intuitive, leverages standard browser and CDN caching, uses native HTTP status codes (200, 404, 500).
The Friction: Multiple network roundtrips (under-fetching) or downloading unused fields (over-fetching).
React REST Code Example:
// Fetching with REST
async function loadUserDataREST(userId) {
const [userRes, postsRes] = await Promise.all([
fetch(`https://playground.nileslabs.com/api/v1/users/${userId}`),
fetch(`https://playground.nileslabs.com/api/v1/users/${userId}/posts?_limit=3`)
]);
const user = await userRes.json();
const posts = await postsRes.json();
return { user, posts };
}
Approach 2: The GraphQL Workflow
In GraphQL, client applications query a single endpoint (/api/v1/graphql) using a declarative query language that requests only the exact fields needed.
Fetching Data in GraphQL:
With GraphQL, the entire nested data graph is retrieved in a single POST request:
query GetUserProfile($userId: ID!) {
user(id: $userId) {
name
email
company {
name
}
posts {
id
title
comments {
id
name
body
}
}
}
}
The Advantage: Zero over-fetching, single network roundtrip, strictly typed schema.
The Friction: All requests are POST to /graphql, requiring specialized client libraries (Apollo Client, URQL, or custom fetchers) to handle caching and error parsing.
React GraphQL Code Example:
// Fetching with GraphQL
async function loadUserDataGraphQL(userId) {
const query = `
query GetUser($id: ID!) {
user(id: $id) {
name
email
company { name }
posts {
id
title
}
}
}
`;
const response = await fetch('https://playground.nileslabs.com/api/v1/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query,
variables: { id: userId }
})
});
const { data, errors } = await response.json();
if (errors) throw new Error(errors[0].message);
return data.user;
}
Comparing CRUD Mutations: REST vs. GraphQL
Let's compare creating a new post:
REST Mutation (POST /api/v1/posts):
const res = await fetch('https://playground.nileslabs.com/api/v1/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'New Post via REST',
body: 'Content...',
user_id: 1
})
});
const createdPost = await res.json();
GraphQL Mutation (mutation CreatePost):
const mutation = `
mutation AddPost($title: String!, $body: String!, $user_id: ID!) {
createPost(title: $title, body: $body, user_id: $user_id) {
id
title
body
user {
name
}
}
}
`;
const res = await fetch('https://playground.nileslabs.com/api/v1/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: mutation,
variables: { title: 'New Post via GraphQL', body: 'Content...', user_id: 1 }
})
});
const { data } = await res.json();
const createdPost = data.createPost;
Notice the key difference: in GraphQL, you can request nested fields (like user { name }) immediately within the mutation response, eliminating follow-up queries.
When to Choose REST vs. GraphQL
| Decision Factor | Choose REST | Choose GraphQL |
|---|---|---|
| Project Complexity | Simple to moderate CRUD apps | Complex apps with deeply nested relations |
| Caching Requirements | Heavy reliance on HTTP/CDN caching | Client-side normalized caching (Apollo / Urql) |
| Bandwidth Sensitivity | Standard web applications | Mobile apps where every byte counts |
| Learning Curve | Lowest (Standard browser fetch) |
Requires understanding schemas & queries |
| Tooling Overhead | Zero tooling required | Code generation, schema compilation |
Testing Both in a Single Sandbox
Finding a sandbox that supports both modern REST filtering and a live GraphQL gateway with mutation persistence is usually difficult.
Playground API by Niles Labs offers dual REST and GraphQL gateways over the exact same underlying dataset:
-
REST Endpoints:
https://playground.nileslabs.com/api/v1/posts -
GraphQL Endpoint:
https://playground.nileslabs.com/api/v1/graphql -
Interactive GraphiQL IDE: Visit
https://playground.nileslabs.com/api/v1/graphqlin your browser to inspect the full schema, types, and documentation interactively.
Any record created via GraphQL is instantly accessible via REST GET /posts, and vice versa.
Conclusion
Neither REST nor GraphQL is universally "better"—they are tools tailored for different architectural priorities. By testing both approaches against a unified, stateful sandbox, you can make informed decisions based on concrete code and real network performance rather than internet dogma.
Compare REST and GraphQL hands-on with Playground API by Niles Labs.
Top comments (0)