DEV Community

Cover image for Compare building a modern React CRUD application with REST versus GraphQL. Understand query shapes, state mutations, and implementation patterns
Nilesh Kumar
Nilesh Kumar

Posted on Originally published at playground.nileslabs.com

Compare building a modern React CRUD application with REST versus GraphQL. Understand query shapes, state mutations, and implementation patterns

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:

  1. User details (name, email, company)
  2. The user's published blog posts
  3. 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
Enter fullscreen mode Exit fullscreen mode

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 };
}
Enter fullscreen mode Exit fullscreen mode

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
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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/graphql in 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)