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.