DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

GraphQL Client-Side: Queries, Mutations, Caching, and Debugging in Firefox DevTools

GraphQL gives clients precise control over what data they fetch — no more over-fetching (getting unused fields) or under-fetching (making multiple requests). Firebase supports using GraphQL in browsers; Firefox DevTools makes it easy to inspect GraphQL queries. This guide covers client-side GraphQL with practical patterns.

What is GraphQL?

GraphQL is a query language for APIs. Instead of multiple REST endpoints, you send a single query describing exactly what you need:

# REST: GET /user/1, GET /user/1/posts, GET /post/5/comments — 3 requests
# GraphQL: one request, get everything
query {
  user(id: 1) {
    name
    email
    posts(limit: 5) {
      title
      publishedAt
      comments {
        text
        author {
          name
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The server returns exactly this shape — no extra fields, no missing fields.

Making GraphQL Requests

GraphQL uses standard HTTP POST:

async function graphqlRequest(query, variables = {}) {
  const response = await fetch('https://api.example.com/graphql', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${getToken()}`,
    },
    body: JSON.stringify({ query, variables }),
  });

  const { data, errors } = await response.json();

  if (errors) {
    throw new GraphQLError(errors);
  }

  return data;
}

// Usage
const { user } = await graphqlRequest(`
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
    }
  }
`, { id: '123' });
Enter fullscreen mode Exit fullscreen mode

Queries vs. Mutations vs. Subscriptions

# Query: read data (GET equivalent)
query GetTodos {
  todos {
    id
    title
    completed
  }
}

# Mutation: write data (POST/PUT/DELETE equivalent)
mutation CreateTodo($title: String!) {
  createTodo(input: { title: $title }) {
    id
    title
    completed
  }
}

# Subscription: real-time data (WebSocket)
subscription OnTodoUpdated {
  todoUpdated {
    id
    title
    completed
  }
}
Enter fullscreen mode Exit fullscreen mode

Fragments: Reusable Field Sets

fragment UserFields on User {
  id
  name
  email
  avatarUrl
}

query GetUsers {
  users {
    ...UserFields
    posts {
      title
    }
  }
}

query GetUser($id: ID!) {
  user(id: $id) {
    ...UserFields
    bio
  }
}
Enter fullscreen mode Exit fullscreen mode

Using Apollo Client

Apollo Client is the most popular GraphQL client for React:

import { ApolloClient, InMemoryCache, gql, useQuery, useMutation } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://api.example.com/graphql',
  cache: new InMemoryCache(),
  headers: { Authorization: `Bearer ${token}` },
});

// React hooks
const GET_TODOS = gql`
  query GetTodos {
    todos { id title completed }
  }
`;

function TodoList() {
  const { loading, error, data } = useQuery(GET_TODOS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.todos.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

Using urql (Lighter Alternative)

import { createClient, cacheExchange, fetchExchange } from 'urql';

const client = createClient({
  url: 'https://api.example.com/graphql',
  exchanges: [cacheExchange, fetchExchange],
});
Enter fullscreen mode Exit fullscreen mode

Caching Strategies

GraphQL clients cache query results. Apollo's InMemoryCache normalizes by id:

const cache = new InMemoryCache({
  typePolicies: {
    Todo: {
      keyFields: ['id'], // Normalize todos by id
    },
    Query: {
      fields: {
        todos: {
          // Merge paginated results
          merge(existing = [], incoming) {
            return [...existing, ...incoming];
          },
        },
      },
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Optimistic Updates

Show UI changes immediately, before server confirms:

const [updateTodo] = useMutation(UPDATE_TODO, {
  optimisticResponse: {
    updateTodo: {
      __typename: 'Todo',
      id: todo.id,
      completed: !todo.completed, // Assume success
    },
  },
  // If mutation fails, cache automatically reverts
});
Enter fullscreen mode Exit fullscreen mode

Debugging GraphQL in Firefox

Firefox DevTools Network panel shows GraphQL requests:

  1. Open DevTools → Network → filter to "XHR"
  2. Click any GraphQL request
  3. Request body shows the query + variables as JSON
  4. Response shows the data/errors

To see the full query:

POST https://api.example.com/graphql
Body: {"query":"query GetTodos { todos { id title } }","variables":{}}
Enter fullscreen mode Exit fullscreen mode

For complex GraphQL operations with Apollo, the Apollo DevTools browser extension adds a dedicated panel showing:

  • Active queries and their results
  • Cache contents
  • Mutation history

Error Handling

GraphQL returns HTTP 200 even for errors — check the errors field:

const { data, errors } = await response.json();

if (errors) {
  errors.forEach(error => {
    console.error(error.message);
    console.error(error.locations); // Line/column in query
    console.error(error.path);      // Which field caused the error
    console.error(error.extensions?.code); // Custom error code
  });
}

// Partial success: data can be non-null even when errors exist
// (partial results with errors for failed fields)
Enter fullscreen mode Exit fullscreen mode

GraphQL vs REST: When to Choose

Choose GraphQL when:

  • Frontend needs vary across clients (mobile vs. web vs. partner API)
  • Over-fetching is causing performance issues
  • Rapid product iteration (add fields without API versioning)
  • Real-time subscriptions needed

Choose REST when:

  • Simple CRUD with stable schemas
  • Third-party integration (REST is universal)
  • HTTP caching is important (GET requests are cacheable; GraphQL POST is not)
  • Team is less familiar with GraphQL

Many mature products use both: REST for stable public APIs, GraphQL for internal client-server communication.

Top comments (0)