DEV Community

Cover image for Zero-Config GraphQL Prototyping: Nested Queries, Relations & State Mutations
Nilesh Kumar
Nilesh Kumar

Posted on Originally published at playground.nileslabs.com

Zero-Config GraphQL Prototyping: Nested Queries, Relations & State Mutations

I still remember the first time I tried to prototype a React app using GraphQL.

I loved the core promise of GraphQL: no more waterfall REST requests, no over-fetching, and the ability to declare the exact relational data tree my UI needed in a single, clean query.

I sat down over a weekend to build a simple social feed prototype with authors, nested posts, and comments. But within an hour, my excitement turned into pure exhaustion.

Before I could write even a single GraphQL query in my React component, I was completely buried under backend plumbing: writing SDL schema definitions, handcrafting recursive resolvers in Apollo Server, configuring Prisma models, setting up SQLite, and fixing N+1 database queries.

What should have been a 2-hour frontend UI experiment turned into an entire weekend of backend boilerplate.

When I looked around the ecosystem for a hosted GraphQL sandbox to prototype with, every free mock API I found only supported basic REST endpoints. There wasn't a single zero-config GraphQL sandbox that supported real nested relational queries AND stateful mutations for a developer's session.

I got so tired of that barrier to entry that I built a native GraphQL gateway directly into Playground API.

In this guide, I'll show you how to prototype complex React interfaces with nested relational queries, execute stateful mutations, and test schemas in an interactive GraphiQL IDE — with zero backend configuration.


The Power of Nested Relational Resolvers

Consider a common frontend UI: an Author Bio card that displays the author's details, their published posts, and the top comments under each post.

In a traditional REST architecture, fetching this data requires multiple roundtrips:

  1. GET /users/1
  2. GET /users/1/posts
  3. GET /posts/1/comments, GET /posts/2/comments, etc.

In GraphQL, you request this entire hierarchy in a single nested query:

query GetAuthorWithPostsAndComments($authorId: ID!) {
  user(id: $authorId) {
    id
    name
    email
    company {
      name
    }
    posts {
      id
      title
      body
      comments {
        id
        name
        body
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The server resolves each child node recursively, returning a clean, perfectly structured JSON tree matching your UI layout.


Exploring the Live GraphQL Gateway & GraphiQL IDE

Playground API by Niles Labs includes a full GraphQL Gateway at:

https://playground.nileslabs.com/api/v1/graphql
Enter fullscreen mode Exit fullscreen mode

If you open this URL in your web browser, you are greeted with a full in-browser GraphiQL IDE.

The interactive IDE includes:

  • Interactive Schema Documentation Explorer: Click through User, Post, Comment, Todo, and Geo types.
  • Real-Time Autocomplete & Validation: Syntax highlighting and auto-completion as you type.
  • Variable Support: Test parameterized queries with dynamic JSON variables.

Executing GraphQL Queries in React with Zero Dependencies

You don't need heavyweight client libraries like Apollo Client or Relay to start querying GraphQL. You can execute queries using standard JavaScript fetch:

// src/services/graphqlClient.js
const GRAPHQL_ENDPOINT = 'https://playground.nileslabs.com/api/v1/graphql';

export async function executeGraphQL(query, variables = {}) {
  const response = await fetch(GRAPHQL_ENDPOINT, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      query,
      variables,
    }),
  });

  const result = await response.json();

  if (result.errors && result.errors.length > 0) {
    throw new Error(result.errors[0].message);
  }

  return result.data;
}
Enter fullscreen mode Exit fullscreen mode

Executing Stateful GraphQL Mutations

Most mock GraphQL endpoints only support read-only queries. If you run a mutation, the data is not actually saved.

With Playground API, mutations operate against the virtual session overlay. When you execute a GraphQL mutation, the new record is saved to your session and is immediately accessible in subsequent GraphQL queries and REST calls.

The GraphQL Mutation:

mutation CreateNewPost($title: String!, $body: String!, $user_id: ID!) {
  createPost(title: $title, body: $body, user_id: $user_id) {
    id
    title
    body
    user {
      name
      email
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Complete React Implementation:

// src/components/GraphQLPostManager.jsx
import React, { useState, useEffect } from 'react';
import { executeGraphQL } from '../services/graphqlClient';

const GET_POSTS_QUERY = `
  query GetRecentPosts {
    posts(limit: 5) {
      id
      title
      body
      user {
        name
      }
    }
  }
`;

const CREATE_POST_MUTATION = `
  mutation AddPost($title: String!, $body: String!, $user_id: ID!) {
    createPost(title: $title, body: $body, user_id: $user_id) {
      id
      title
      body
      user {
        name
      }
    }
  }
`;

export default function GraphQLPostManager() {
  const [posts, setPosts] = useState([]);
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const [loading, setLoading] = useState(true);

  const loadPosts = async () => {
    try {
      setLoading(true);
      const data = await executeGraphQL(GET_POSTS_QUERY);
      setPosts(data.posts);
    } catch (err) {
      console.error(err);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    loadPosts();
  }, []);

  const handleCreate = async (e) => {
    e.preventDefault();
    if (!title.trim()) return;

    try {
      const data = await executeGraphQL(CREATE_POST_MUTATION, {
        title,
        body,
        user_id: 1,
      });
      // Prepend the created post: it persisted in the session!
      setPosts(current => [data.createPost, ...current]);
      setTitle('');
      setBody('');
    } catch (err) {
      alert(`Mutation failed: ${err.message}`);
    }
  };

  return (
    <div style={{ maxWidth: '600px', margin: '2rem auto', fontFamily: 'sans-serif' }}>
      <h2>⚡ GraphQL Sandbox Manager</h2>

      {/* Mutation Form */}
      <form onSubmit={handleCreate} style={{ display: 'flex', flexDirection: 'column', gap: '8px', marginBottom: '2rem' }}>
        <input
          type="text"
          placeholder="Post title..."
          value={title}
          onChange={e => setTitle(e.target.value)}
          required
          style={{ padding: '8px', borderRadius: '4px', border: '1px solid #cbd5e1' }}
        />
        <textarea
          placeholder="Post content..."
          value={body}
          onChange={e => setBody(e.target.value)}
          rows={3}
          style={{ padding: '8px', borderRadius: '4px', border: '1px solid #cbd5e1' }}
        />
        <button type="submit" style={{ padding: '8px', background: '#e11d48', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>
          + Execute Mutation
        </button>
      </form>

      {/* Query Results */}
      {loading ? (
        <p>Fetching GraphQL data graph...</p>
      ) : (
        <div>
          {posts.map(post => (
            <div key={post.id} style={{ border: '1px solid #e2e8f0', borderRadius: '6px', padding: '1rem', marginBottom: '1rem' }}>
              <h4 style={{ margin: '0 0 4px 0' }}>#{post.id} {post.title}</h4>
              <p style={{ margin: '0 0 8px 0', color: '#64748b' }}>{post.body}</p>
              <small style={{ color: '#0284c7' }}>Author: {post.user?.name || 'Anonymous'}</small>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

3 Core Tips for GraphQL Frontend Prototyping

  1. Leverage the In-Browser GraphiQL Explorer: Before writing frontend queries in JSX, construct and test them inside https://playground.nileslabs.com/api/v1/graphql to verify field names and query execution.
  2. Use Variables for Dynamic Inputs: Never concatenate strings into GraphQL query strings (query { user(id: ${id}) }). Always use GraphQL variables (query ($id: ID!) { user(id: $id) }) to prevent syntax parsing errors and injection vulnerabilities.
  3. Verify Mutation Responses: Always ask for the fields your UI needs immediately after a mutation executes so you can update client caches without needing an extra roundtrip fetch.

Conclusion

GraphQL makes data fetching declarative, type-safe, and elegant. With a zero-configuration GraphQL gateway that supports nested relations and stateful mutations, you can master and prototype GraphQL interfaces without spending hours configuring backend servers.

Test nested queries and mutations today on the Playground API GraphQL Gateway.

Top comments (0)