Back to skills
SkillHub ClubWrite Technical DocsFull StackData / AITech Writer

graphql-operations

Guide for writing GraphQL operations (queries, mutations, fragments) following best practices. Use this skill when: (1) writing GraphQL queries or mutations, (2) organizing operations with fragments, (3) optimizing data fetching patterns, (4) setting up type generation or linting, (5) reviewing operations for efficiency.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
36
Hot score
90
Updated
March 20, 2026
Overall rating
C2.6
Composite score
2.6
Best-practice grade
C62.8

Install command

npx @skill-hub/cli install apollographql-skills-graphql-operations

Repository

apollographql/skills

Skill path: skills/graphql-operations

Guide for writing GraphQL operations (queries, mutations, fragments) following best practices. Use this skill when: (1) writing GraphQL queries or mutations, (2) organizing operations with fragments, (3) optimizing data fetching patterns, (4) setting up type generation or linting, (5) reviewing operations for efficiency.

Open repository

Best for

Primary workflow: Write Technical Docs.

Technical facets: Full Stack, Data / AI, Tech Writer.

Target audience: everyone.

License: MIT.

Original source

Catalog source: SkillHub Club.

Repository owner: apollographql.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install graphql-operations into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/apollographql/skills before adding graphql-operations to shared team environments
  • Use graphql-operations for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: graphql-operations
description: >
  Guide for writing GraphQL operations (queries, mutations, fragments) following best practices. Use this skill when:
  (1) writing GraphQL queries or mutations,
  (2) organizing operations with fragments,
  (3) optimizing data fetching patterns,
  (4) setting up type generation or linting,
  (5) reviewing operations for efficiency.
license: MIT
compatibility: Any GraphQL client (Apollo Client, urql, Relay, etc.)
metadata:
  author: apollographql
  version: "1.0.0"
allowed-tools: Bash(npm:*) Bash(npx:*) Read Write Edit Glob Grep
---

# GraphQL Operations Guide

This guide covers best practices for writing GraphQL operations (queries, mutations, subscriptions) as a client developer. Well-written operations are efficient, type-safe, and maintainable.

## Operation Basics

### Query Structure

```graphql
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
  }
}
```

### Mutation Structure

```graphql
mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    title
    createdAt
  }
}
```

### Subscription Structure

```graphql
subscription OnMessageReceived($channelId: ID!) {
  messageReceived(channelId: $channelId) {
    id
    content
    sender {
      id
      name
    }
  }
}
```

## Quick Reference

### Operation Naming

| Pattern      | Example                                     |
| ------------ | ------------------------------------------- |
| Query        | `GetUser`, `ListPosts`, `SearchProducts`    |
| Mutation     | `CreateUser`, `UpdatePost`, `DeleteComment` |
| Subscription | `OnMessageReceived`, `OnUserStatusChanged`  |

### Variable Syntax

```graphql
# Required variable
query GetUser($id: ID!) { ... }

# Optional variable with default
query ListPosts($first: Int = 20) { ... }

# Multiple variables
query SearchPosts($query: String!, $status: PostStatus, $first: Int = 10) { ... }
```

### Fragment Syntax

```graphql
# Define fragment
fragment UserBasicInfo on User {
  id
  name
  avatarUrl
}

# Use fragment
query GetUser($id: ID!) {
  user(id: $id) {
    ...UserBasicInfo
    email
  }
}
```

### Directives

```graphql
query GetUser($id: ID!, $includeEmail: Boolean!) {
  user(id: $id) {
    id
    name
    email @include(if: $includeEmail)
  }
}

query GetPosts($skipDrafts: Boolean!) {
  posts {
    id
    title
    draft @skip(if: $skipDrafts)
  }
}
```

## Key Principles

### 1. Request Only What You Need

```graphql
# Good: Specific fields
query GetUserName($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

# Avoid: Over-fetching
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
    bio
    posts {
      id
      title
      content
      comments {
        id
      }
    }
    followers {
      id
      name
    }
    # ... many unused fields
  }
}
```

### 2. Name All Operations

```graphql
# Good: Named operation
query GetUserPosts($userId: ID!) {
  user(id: $userId) {
    posts {
      id
      title
    }
  }
}

# Avoid: Anonymous operation
query {
  user(id: "123") {
    posts {
      id
      title
    }
  }
}
```

### 3. Use Variables, Not Inline Values

```graphql
# Good: Variables
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

# Avoid: Hardcoded values
query {
  user(id: "123") {
    id
    name
  }
}
```

### 4. Colocate Fragments with Components

```tsx
// UserAvatar.tsx
export const USER_AVATAR_FRAGMENT = gql`
  fragment UserAvatar on User {
    id
    name
    avatarUrl
  }
`;

function UserAvatar({ user }) {
  return <img src={user.avatarUrl} alt={user.name} />;
}
```

## Reference Files

Detailed documentation for specific topics:

- [Queries](references/queries.md) - Query patterns and optimization
- [Mutations](references/mutations.md) - Mutation patterns and error handling
- [Fragments](references/fragments.md) - Fragment organization and reuse
- [Variables](references/variables.md) - Variable usage and types
- [Tooling](references/tooling.md) - Code generation and linting

## Ground Rules

- ALWAYS name your operations (no anonymous queries/mutations)
- ALWAYS use variables for dynamic values
- ALWAYS request only the fields you need
- ALWAYS include `id` field for cacheable types
- NEVER hardcode values in operations
- NEVER duplicate field selections across files
- PREFER fragments for reusable field selections
- PREFER colocating fragments with components
- USE descriptive operation names that reflect purpose
- USE `@include`/`@skip` for conditional fields


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/queries.md

```markdown
# Query Patterns

This reference covers patterns for writing effective GraphQL queries.

## Table of Contents

- [Query Structure](#query-structure)
- [Field Selection](#field-selection)
- [Aliases](#aliases)
- [Directives](#directives)
- [Query Naming](#query-naming)
- [Query Organization](#query-organization)
- [Performance Optimization](#performance-optimization)

## Query Structure

### Basic Query

```graphql
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
  }
}
```

Components:

- `query` - Operation type
- `GetUser` - Operation name
- `($id: ID!)` - Variable definitions
- `user(id: $id)` - Field with argument
- `{ id name email }` - Selection set

### Query with Multiple Root Fields

```graphql
query GetDashboardData($userId: ID!) {
  user(id: $userId) {
    id
    name
  }
  notifications(first: 5) {
    id
    message
  }
  stats {
    totalPosts
    totalComments
  }
}
```

### Nested Queries

```graphql
query GetUserWithPosts($userId: ID!) {
  user(id: $userId) {
    id
    name
    posts(first: 10) {
      edges {
        node {
          id
          title
          comments(first: 3) {
            edges {
              node {
                id
                body
              }
            }
          }
        }
      }
    }
  }
}
```

## Field Selection

### Request Only Needed Fields

```graphql
# For a user card component
query GetUserCard($id: ID!) {
  user(id: $id) {
    id
    name
    avatarUrl
    # Don't request email, bio, etc. if not displayed
  }
}
```

### Always Include ID Fields

Include `id` for any type you might cache or refetch:

```graphql
query GetPost($id: ID!) {
  post(id: $id) {
    id # Always include for caching
    title
    author {
      id # Include for author cache entry
      name
    }
  }
}
```

### Selecting Connections

For paginated data, request what you need:

```graphql
query GetUserPosts($userId: ID!, $first: Int!, $after: String) {
  user(id: $userId) {
    id
    posts(first: $first, after: $after) {
      edges {
        node {
          id
          title
          excerpt
        }
        cursor # Only if implementing infinite scroll
      }
      pageInfo {
        hasNextPage
        endCursor
      }
      totalCount # Only if displaying total
    }
  }
}
```

## Aliases

### Basic Alias

Rename fields in the response:

```graphql
query GetUserNames($id: ID!) {
  user(id: $id) {
    userId: id
    displayName: name
  }
}

# Response: { user: { userId: "123", displayName: "John" } }
```

### Query Same Field Multiple Times

```graphql
query GetMultipleUsers {
  admin: user(id: "1") {
    id
    name
  }
  moderator: user(id: "2") {
    id
    name
  }
  currentUser: user(id: "3") {
    id
    name
  }
}
```

### Alias with Different Arguments

```graphql
query GetPostsByStatus($userId: ID!) {
  user(id: $userId) {
    id
    publishedPosts: posts(status: PUBLISHED, first: 5) {
      edges {
        node {
          id
          title
        }
      }
    }
    draftPosts: posts(status: DRAFT, first: 5) {
      edges {
        node {
          id
          title
        }
      }
    }
  }
}
```

## Directives

### @include Directive

Include field only if condition is true:

```graphql
query GetUser($id: ID!, $includeEmail: Boolean!) {
  user(id: $id) {
    id
    name
    email @include(if: $includeEmail)
  }
}

# Variables: { id: "123", includeEmail: true }
# Returns email field

# Variables: { id: "123", includeEmail: false }
# Does not return email field
```

### @skip Directive

Skip field if condition is true:

```graphql
query GetPost($id: ID!, $isPreview: Boolean!) {
  post(id: $id) {
    id
    title
    content @skip(if: $isPreview)
    excerpt
  }
}
```

### Directives on Fragments

```graphql
query GetUser($id: ID!, $expanded: Boolean!) {
  user(id: $id) {
    id
    name
    ...UserDetails @include(if: $expanded)
  }
}

fragment UserDetails on User {
  bio
  website
  socialLinks {
    platform
    url
  }
}
```

### Combining Directives

```graphql
query GetPost($id: ID!, $showComments: Boolean!, $hideAuthor: Boolean!) {
  post(id: $id) {
    id
    title
    author @skip(if: $hideAuthor) {
      id
      name
    }
    comments(first: 10) @include(if: $showComments) {
      edges {
        node {
          id
          body
        }
      }
    }
  }
}
```

## Query Naming

### Naming Conventions

| Purpose               | Pattern            | Examples                             |
| --------------------- | ------------------ | ------------------------------------ |
| Fetch single item     | `Get{Type}`        | `GetUser`, `GetPost`                 |
| Fetch list            | `List{Types}`      | `ListUsers`, `ListPosts`             |
| Search                | `Search{Types}`    | `SearchUsers`, `SearchProducts`      |
| Fetch for specific UI | `Get{Feature}Data` | `GetDashboardData`, `GetProfilePage` |

### Good Names

```graphql
query GetUserProfile($id: ID!) { ... }
query ListRecentPosts($first: Int!) { ... }
query SearchProducts($query: String!) { ... }
query GetOrderDetails($orderId: ID!) { ... }
query GetHomeFeed($userId: ID!) { ... }
```

### Avoid Generic Names

```graphql
# Avoid
query Data { ... }
query Query1 { ... }
query FetchStuff { ... }

# Prefer
query GetCurrentUser { ... }
query ListActiveProjects { ... }
query SearchCustomers($query: String!) { ... }
```

## Query Organization

### One Query Per File

```
src/
  graphql/
    queries/
      GetUser.graphql
      ListPosts.graphql
      SearchProducts.graphql
```

```graphql
# GetUser.graphql
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
  }
}
```

### Colocate with Components

```
src/
  components/
    UserProfile/
      UserProfile.tsx
      UserProfile.graphql
      UserProfile.test.tsx
```

### Import and Use

```typescript
// With graphql-tag
import { gql } from "@apollo/client";

export const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
    }
  }
`;

// With .graphql files (requires loader)
import { GetUserDocument } from "./UserProfile.generated";
```

## Performance Optimization

### Avoid Over-fetching

Only request fields used by your component:

```graphql
# For a list view - minimal fields
query ListPostsForIndex {
  posts(first: 20) {
    edges {
      node {
        id
        title
        excerpt
        author { name }
      }
    }
  }
}

# For detail view - more fields
query GetPostDetail($id: ID!) {
  post(id: $id) {
    id
    title
    content
    publishedAt
    author {
      id
      name
      bio
      avatarUrl
    }
    comments(first: 10) { ... }
  }
}
```

### Use Pagination

Never fetch unbounded lists:

```graphql
# Avoid
query GetAllPosts {
  posts {
    # Could return thousands
    id
    title
  }
}

# Prefer
query GetPosts($first: Int = 20, $after: String) {
  posts(first: $first, after: $after) {
    edges {
      node {
        id
        title
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

### Batch Related Queries

Fetch related data in one request:

```graphql
# Instead of multiple queries
query GetDashboard($userId: ID!) {
  user(id: $userId) {
    id
    name
  }
  recentPosts: posts(first: 5, orderBy: { field: CREATED_AT, direction: DESC }) {
    edges {
      node {
        id
        title
      }
    }
  }
  notifications(first: 10, unreadOnly: true) {
    edges {
      node {
        id
        message
      }
    }
  }
}
```

### Use Fragments for Repeated Selections

```graphql
query GetPostsWithAuthors {
  posts(first: 10) {
    edges {
      node {
        id
        title
        author {
          ...AuthorInfo
        }
      }
    }
  }
  featuredPost {
    id
    title
    author {
      ...AuthorInfo
    }
  }
}

fragment AuthorInfo on User {
  id
  name
  avatarUrl
}
```

```

### references/mutations.md

```markdown
# Mutation Patterns

This reference covers patterns for writing effective GraphQL mutations.

## Table of Contents

- [Mutation Structure](#mutation-structure)
- [Input Patterns](#input-patterns)
- [Response Selection](#response-selection)
- [Error Handling](#error-handling)
- [Optimistic Updates](#optimistic-updates)
- [Mutation Naming](#mutation-naming)

## Mutation Structure

### Basic Mutation

```graphql
mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    title
    createdAt
  }
}
```

Variables:

```json
{
  "input": {
    "title": "My Post",
    "content": "Post content..."
  }
}
```

### Mutation with Multiple Arguments

```graphql
mutation UpdatePost($id: ID!, $input: UpdatePostInput!) {
  updatePost(id: $id, input: $input) {
    id
    title
    updatedAt
  }
}
```

### Multiple Mutations

Execute multiple mutations in one request (sequential execution):

```graphql
mutation SetupUserProfile($userId: ID!, $profileInput: ProfileInput!, $settingsInput: SettingsInput!) {
  updateProfile(userId: $userId, input: $profileInput) {
    id
    bio
  }
  updateSettings(userId: $userId, input: $settingsInput) {
    id
    theme
    notifications
  }
}
```

## Input Patterns

### Single Input Object

Recommended pattern - single input argument:

```graphql
mutation CreateUser($input: CreateUserInput!) {
  createUser(input: $input) {
    id
    email
  }
}
```

```json
{
  "input": {
    "email": "[email protected]",
    "name": "John Doe",
    "password": "secret123"
  }
}
```

### Nested Input Objects

```graphql
mutation CreateOrder($input: CreateOrderInput!) {
  createOrder(input: $input) {
    id
    total
  }
}
```

```json
{
  "input": {
    "items": [
      { "productId": "prod_1", "quantity": 2 },
      { "productId": "prod_2", "quantity": 1 }
    ],
    "shippingAddress": {
      "street": "123 Main St",
      "city": "New York",
      "country": "US"
    }
  }
}
```

### Optional Fields

```graphql
mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
  updateUser(id: $id, input: $input) {
    id
    name
    bio
  }
}
```

```json
{
  "id": "user_123",
  "input": {
    "name": "New Name"
    // bio not included - won't be changed
  }
}
```

## Response Selection

### Return the Modified Object

Always return the mutated object with updated fields:

```graphql
mutation UpdatePost($id: ID!, $input: UpdatePostInput!) {
  updatePost(id: $id, input: $input) {
    id
    title
    content
    updatedAt # Server-set field
  }
}
```

### Return Related Objects

If mutation affects related data, include it:

```graphql
mutation AddComment($input: AddCommentInput!) {
  addComment(input: $input) {
    id
    body
    post {
      id
      commentCount # Updated count
    }
    author {
      id
      name
    }
  }
}
```

### Return for Cache Updates

Select fields needed to update your cache:

```graphql
mutation DeletePost($id: ID!) {
  deletePost(id: $id) {
    id # Needed to remove from cache
    author {
      id
      postCount # May need to decrement
    }
  }
}
```

### Return Connections for List Updates

```graphql
mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    title
    createdAt
    author {
      id
      posts(first: 1) {
        edges {
          node {
            id
          }
        }
        totalCount
      }
    }
  }
}
```

## Error Handling

### Query Result Unions

When schema uses union types for errors:

```graphql
mutation CreateUser($input: CreateUserInput!) {
  createUser(input: $input) {
    ... on CreateUserSuccess {
      user {
        id
        email
      }
    }
    ... on ValidationError {
      message
      field
    }
    ... on EmailAlreadyExists {
      message
      existingUserId
    }
  }
}
```

### Handle All Cases

```typescript
const result = await client.mutate({
  mutation: CREATE_USER,
  variables: { input },
});

const { createUser } = result.data;

switch (createUser.__typename) {
  case "CreateUserSuccess":
    // Handle success
    return createUser.user;
  case "ValidationError":
    // Handle validation error
    throw new ValidationError(createUser.field, createUser.message);
  case "EmailAlreadyExists":
    // Handle specific business error
    throw new EmailExistsError(createUser.existingUserId);
}
```

### GraphQL Errors

Handle network and GraphQL errors:

```typescript
try {
  const result = await client.mutate({
    mutation: CREATE_POST,
    variables: { input },
  });
  return result.data.createPost;
} catch (error) {
  if (error.graphQLErrors?.length) {
    // Handle GraphQL errors
    const gqlError = error.graphQLErrors[0];
    if (gqlError.extensions?.code === "UNAUTHENTICATED") {
      // Redirect to login
    }
  }
  if (error.networkError) {
    // Handle network error
  }
  throw error;
}
```

## Optimistic Updates

### Select Fields for Optimistic Response

Include all fields that will display immediately:

```graphql
mutation LikePost($postId: ID!) {
  likePost(postId: $postId) {
    id
    likeCount
    isLikedByViewer
  }
}
```

```typescript
client.mutate({
  mutation: LIKE_POST,
  variables: { postId: "post_123" },
  optimisticResponse: {
    likePost: {
      __typename: "Post",
      id: "post_123",
      likeCount: currentCount + 1,
      isLikedByViewer: true,
    },
  },
});
```

### Include Created IDs

For create mutations, use temporary IDs:

```graphql
mutation AddComment($input: AddCommentInput!) {
  addComment(input: $input) {
    id
    body
    createdAt
    author {
      id
      name
      avatarUrl
    }
  }
}
```

```typescript
client.mutate({
  mutation: ADD_COMMENT,
  variables: { input: { postId, body } },
  optimisticResponse: {
    addComment: {
      __typename: "Comment",
      id: `temp-${Date.now()}`, // Temporary ID
      body,
      createdAt: new Date().toISOString(),
      author: {
        __typename: "User",
        id: currentUser.id,
        name: currentUser.name,
        avatarUrl: currentUser.avatarUrl,
      },
    },
  },
});
```

## Mutation Naming

### Naming Conventions

| Operation    | Pattern              | Examples                        |
| ------------ | -------------------- | ------------------------------- |
| Create       | `Create{Type}`       | `CreateUser`, `CreatePost`      |
| Update       | `Update{Type}`       | `UpdateUser`, `UpdatePost`      |
| Delete       | `Delete{Type}`       | `DeleteUser`, `DeletePost`      |
| Action       | `{Verb}{Type}`       | `PublishPost`, `ArchiveProject` |
| Relationship | `{Add/Remove}{Type}` | `AddTeamMember`, `RemoveTag`    |

### Good Names

```graphql
mutation CreateUser($input: CreateUserInput!) { ... }
mutation UpdateUserProfile($userId: ID!, $input: ProfileInput!) { ... }
mutation DeletePost($id: ID!) { ... }
mutation PublishArticle($id: ID!) { ... }
mutation ArchiveProject($id: ID!) { ... }
mutation AddItemToCart($input: AddItemInput!) { ... }
mutation RemoveTeamMember($teamId: ID!, $userId: ID!) { ... }
mutation FollowUser($userId: ID!) { ... }
mutation MarkNotificationAsRead($id: ID!) { ... }
```

### Operation Name Matches Server

Match client operation name to server mutation:

```graphql
# Server schema
type Mutation {
  createPost(input: CreatePostInput!): Post!
}

# Client operation - name reflects the action
mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    title
  }
}
```

### Context-Specific Names

Add context when same mutation is used differently:

```graphql
# For creating a draft
mutation CreateDraftPost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    title
    status
  }
}

# For creating and publishing immediately
mutation CreateAndPublishPost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    title
    status
    publishedAt
  }
}
```

```

### references/fragments.md

```markdown
# Fragment Patterns

This reference covers patterns for organizing and using GraphQL fragments effectively.

## Table of Contents

- [Fragment Basics](#fragment-basics)
- [Fragment Colocation](#fragment-colocation)
- [Fragment Reuse](#fragment-reuse)
- [Inline Fragments](#inline-fragments)
- [Type Conditions](#type-conditions)
- [Fragment Composition](#fragment-composition)
- [Anti-Patterns](#anti-patterns)

## Fragment Basics

### Defining Fragments

```graphql
fragment UserBasicInfo on User {
  id
  name
  avatarUrl
}
```

### Using Fragments

```graphql
query GetUser($id: ID!) {
  user(id: $id) {
    ...UserBasicInfo
    email
  }
}

fragment UserBasicInfo on User {
  id
  name
  avatarUrl
}
```

### Fragment Spread

The `...` operator spreads fragment fields:

```graphql
query GetPost($id: ID!) {
  post(id: $id) {
    id
    title
    author {
      ...UserBasicInfo # Spreads id, name, avatarUrl
    }
  }
}
```

## Fragment Colocation

### Colocate with Components

Keep fragments next to the components that use them:

```
src/
  components/
    UserAvatar/
      UserAvatar.tsx
      UserAvatar.fragment.graphql
    UserCard/
      UserCard.tsx
      UserCard.fragment.graphql
    PostList/
      PostList.tsx
      PostList.query.graphql
```

### Component Owns Its Data

```tsx
// UserAvatar.tsx
import { gql } from "@apollo/client";

export const USER_AVATAR_FRAGMENT = gql`
  fragment UserAvatar on User {
    id
    name
    avatarUrl
  }
`;

interface UserAvatarProps {
  user: UserAvatarFragment;
}

export function UserAvatar({ user }: UserAvatarProps) {
  return <img src={user.avatarUrl} alt={user.name} className="avatar" />;
}
```

### Parent Composes Fragments

```tsx
// UserCard.tsx
import { gql } from "@apollo/client";
import { USER_AVATAR_FRAGMENT, UserAvatar } from "./UserAvatar";

export const USER_CARD_FRAGMENT = gql`
  fragment UserCard on User {
    id
    name
    bio
    ...UserAvatar
  }
  ${USER_AVATAR_FRAGMENT}
`;

export function UserCard({ user }: { user: UserCardFragment }) {
  return (
    <div className="user-card">
      <UserAvatar user={user} />
      <h3>{user.name}</h3>
      <p>{user.bio}</p>
    </div>
  );
}
```

### Query Uses Component Fragments

```tsx
// UserProfile.tsx
import { gql, useQuery } from "@apollo/client";
import { USER_CARD_FRAGMENT, UserCard } from "./UserCard";

const GET_USER = gql`
  query GetUserProfile($id: ID!) {
    user(id: $id) {
      ...UserCard
      email
      createdAt
    }
  }
  ${USER_CARD_FRAGMENT}
`;

export function UserProfile({ userId }: { userId: string }) {
  const { data } = useQuery(GET_USER, { variables: { id: userId } });

  if (!data) return null;

  return (
    <div>
      <UserCard user={data.user} />
      <p>Email: {data.user.email}</p>
    </div>
  );
}
```

## Fragment Reuse

### Shared Fragments

For common patterns used across many components:

```graphql
# fragments/common.graphql

fragment Timestamps on Node {
  createdAt
  updatedAt
}

fragment PageInfoFields on PageInfo {
  hasNextPage
  hasPreviousPage
  startCursor
  endCursor
}
```

### Domain-Specific Fragments

```graphql
# fragments/user.graphql

fragment UserSummary on User {
  id
  name
  avatarUrl
}

fragment UserProfile on User {
  ...UserSummary
  bio
  location
  website
  socialLinks {
    platform
    url
  }
}

fragment UserWithStats on User {
  ...UserSummary
  followerCount
  followingCount
  postCount
}
```

### Using Shared Fragments

```graphql
query GetPost($id: ID!) {
  post(id: $id) {
    id
    title
    ...Timestamps
    author {
      ...UserSummary
    }
  }
}
```

## Inline Fragments

### Anonymous Inline Fragments

For grouping fields with directives:

```graphql
query GetUser($id: ID!, $includeDetails: Boolean!) {
  user(id: $id) {
    id
    name
    ... @include(if: $includeDetails) {
      email
      bio
      website
    }
  }
}
```

### Inline Fragments on Interfaces

```graphql
query GetNodes($ids: [ID!]!) {
  nodes(ids: $ids) {
    id
    ... on User {
      name
      email
    }
    ... on Post {
      title
      content
    }
  }
}
```

## Type Conditions

### Fragments on Union Types

```graphql
query Search($query: String!) {
  search(query: $query) {
    ... on User {
      id
      name
      avatarUrl
    }
    ... on Post {
      id
      title
      excerpt
    }
    ... on Comment {
      id
      body
      post {
        id
        title
      }
    }
  }
}
```

### Named Fragments for Unions

```graphql
query Search($query: String!) {
  search(query: $query) {
    ...SearchResultUser
    ...SearchResultPost
    ...SearchResultComment
  }
}

fragment SearchResultUser on User {
  id
  name
  avatarUrl
}

fragment SearchResultPost on Post {
  id
  title
  excerpt
  author {
    name
  }
}

fragment SearchResultComment on Comment {
  id
  body
  post {
    id
    title
  }
}
```

### Handling \_\_typename

```typescript
function SearchResult({ result }) {
  switch (result.__typename) {
    case 'User':
      return <UserResult user={result} />;
    case 'Post':
      return <PostResult post={result} />;
    case 'Comment':
      return <CommentResult comment={result} />;
  }
}
```

## Fragment Composition

### Building Up Fragments

```graphql
# Base fragment
fragment PostCore on Post {
  id
  title
  slug
}

# Extended fragment
fragment PostPreview on Post {
  ...PostCore
  excerpt
  featuredImage {
    url
  }
}

# Full fragment
fragment PostFull on Post {
  ...PostPreview
  content
  publishedAt
  author {
    ...UserSummary
  }
  tags {
    id
    name
  }
}
```

### Fragments in Fragments

```graphql
fragment CommentWithAuthor on Comment {
  id
  body
  createdAt
  author {
    ...UserSummary
  }
}

fragment PostWithComments on Post {
  id
  title
  comments(first: 10) {
    edges {
      node {
        ...CommentWithAuthor
      }
    }
  }
}
```

### Fragment Spread Order

Order doesn't matter - fields are merged:

```graphql
query GetUser($id: ID!) {
  user(id: $id) {
    ...UserProfile
    ...UserStats
    # Both fragments' fields are included
  }
}
```

## Anti-Patterns

### Avoid Giant Fragments

```graphql
# Bad: Too many fields, not all needed everywhere
fragment UserEverything on User {
  id
  name
  email
  bio
  avatarUrl
  coverImage
  website
  location
  socialLinks { ... }
  posts { ... }
  followers { ... }
  following { ... }
  # ... 50 more fields
}

# Good: Focused fragments for specific uses
fragment UserAvatar on User {
  id
  name
  avatarUrl
}

fragment UserProfile on User {
  id
  name
  bio
  avatarUrl
  website
  location
}
```

### Avoid Unused Fragment Fields

```graphql
# Bad: Component only uses name and avatarUrl
fragment UserInfo on User {
  id
  name
  email # unused
  avatarUrl
  bio # unused
  website # unused
}

# Good: Only request what's needed
fragment UserInfo on User {
  id
  name
  avatarUrl
}
```

### Avoid Deeply Nested Fragments

```graphql
# Bad: Hard to understand what's being fetched
fragment Level1 on User {
  ...Level2
}
fragment Level2 on User {
  ...Level3
}
fragment Level3 on User {
  ...Level4
}
# ... continues

# Good: Keep nesting shallow
fragment UserWithPosts on User {
  id
  name
  posts {
    ...PostPreview
  }
}
```

### Avoid Circular Fragment Dependencies

```graphql
# Bad: Circular reference (won't work)
fragment UserWithPosts on User {
  posts {
    ...PostWithAuthor
  }
}

fragment PostWithAuthor on Post {
  author {
    ...UserWithPosts # Circular!
  }
}

# Good: Break the cycle
fragment UserWithPosts on User {
  posts {
    ...PostPreview
  }
}

fragment PostWithAuthor on Post {
  author {
    ...UserSummary # Different fragment, no cycle
  }
}
```

```

### references/variables.md

```markdown
# Variable Patterns

This reference covers patterns for using variables in GraphQL operations.

## Table of Contents

- [Variable Basics](#variable-basics)
- [Variable Types](#variable-types)
- [Default Values](#default-values)
- [Complex Inputs](#complex-inputs)
- [Best Practices](#best-practices)

## Variable Basics

### Declaring Variables

Variables are declared in the operation definition:

```graphql
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}
```

### Using Variables

Variables are referenced with `$` prefix:

```graphql
query GetUser($id: ID!) {
  user(id: $id) {
    # $id used here
    id
    name
  }
}
```

### Passing Variables

Variables are passed as a separate JSON object:

```typescript
const { data } = await client.query({
  query: GET_USER,
  variables: {
    id: "user_123",
  },
});
```

### Multiple Variables

```graphql
query SearchPosts($query: String!, $status: PostStatus, $first: Int!, $after: String) {
  searchPosts(query: $query, status: $status, first: $first, after: $after) {
    edges {
      node {
        id
        title
      }
    }
  }
}
```

```json
{
  "query": "graphql",
  "status": "PUBLISHED",
  "first": 10,
  "after": "cursor_abc"
}
```

## Variable Types

### Scalar Types

```graphql
query Example(
  $id: ID!
  $name: String!
  $count: Int!
  $price: Float!
  $active: Boolean!
) {
  # ...
}
```

### Custom Scalar Types

```graphql
query Example(
  $date: DateTime!
  $email: Email!
  $url: URL!
) {
  # ...
}
```

### Enum Types

```graphql
query GetPosts($status: PostStatus!) {
  posts(status: $status) {
    id
    title
  }
}
```

```json
{
  "status": "PUBLISHED"
}
```

### List Types

```graphql
query GetUsers($ids: [ID!]!) {
  users(ids: $ids) {
    id
    name
  }
}
```

```json
{
  "ids": ["user_1", "user_2", "user_3"]
}
```

### Input Object Types

```graphql
mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
  }
}
```

```json
{
  "input": {
    "title": "My Post",
    "content": "Post content...",
    "tags": ["graphql", "api"]
  }
}
```

### Required vs Optional

```graphql
query Example(
  $required: String!     # Must be provided, cannot be null
  $optional: String      # Can be omitted or null
  $requiredList: [String!]!  # List required, items required
  $optionalList: [String]    # List optional, items optional
) {
  # ...
}
```

## Default Values

### Simple Defaults

```graphql
query GetPosts($first: Int = 10, $status: PostStatus = PUBLISHED) {
  posts(first: $first, status: $status) {
    id
    title
  }
}
```

If not provided, uses defaults:

```json
{}
// Equivalent to: { "first": 10, "status": "PUBLISHED" }
```

Override defaults:

```json
{
  "first": 20
}
// Uses first: 20, status: PUBLISHED (default)
```

### Defaults with Optional Variables

```graphql
# Variable is optional (no !) but has default
query GetPosts($first: Int = 10) {
  posts(first: $first) {
    id
  }
}
```

### Defaults for Complex Types

```graphql
query GetPosts($orderBy: PostOrderInput = { field: CREATED_AT, direction: DESC }) {
  posts(orderBy: $orderBy) {
    id
    title
  }
}
```

### When to Use Defaults

Use defaults for:

- Pagination limits (`first: Int = 20`)
- Sort order (`direction: SortDirection = DESC`)
- Common filter values (`status: Status = ACTIVE`)
- Feature flags (`includeArchived: Boolean = false`)

## Complex Inputs

### Nested Input Objects

```graphql
mutation CreateOrder($input: CreateOrderInput!) {
  createOrder(input: $input) {
    id
    total
  }
}
```

```json
{
  "input": {
    "customer": {
      "email": "[email protected]",
      "name": "John Doe"
    },
    "items": [
      { "productId": "prod_1", "quantity": 2 },
      { "productId": "prod_2", "quantity": 1 }
    ],
    "shippingAddress": {
      "street": "123 Main St",
      "city": "New York",
      "state": "NY",
      "zipCode": "10001",
      "country": "US"
    }
  }
}
```

### Lists of Input Objects

```graphql
mutation BulkCreateUsers($inputs: [CreateUserInput!]!) {
  bulkCreateUsers(inputs: $inputs) {
    id
    email
  }
}
```

```json
{
  "inputs": [
    { "email": "[email protected]", "name": "User 1" },
    { "email": "[email protected]", "name": "User 2" },
    { "email": "[email protected]", "name": "User 3" }
  ]
}
```

### Filter Inputs

```graphql
query SearchProducts($filter: ProductFilter!) {
  products(filter: $filter) {
    id
    name
    price
  }
}
```

```json
{
  "filter": {
    "category": "ELECTRONICS",
    "priceRange": {
      "min": 100,
      "max": 500
    },
    "inStock": true,
    "tags": ["featured", "sale"]
  }
}
```

## Best Practices

### Always Use Variables for Dynamic Values

```graphql
# Good: Uses variable
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

# Bad: Hardcoded value
query GetUser {
  user(id: "123") {
    id
    name
  }
}
```

### Match Variable Names to Arguments

```graphql
# Good: Clear relationship
query GetUser($userId: ID!) {
  user(id: $userId) {
    id
  }
}

# Also good: Same name
query GetUser($id: ID!) {
  user(id: $id) {
    id
  }
}

# Bad: Confusing names
query GetUser($x: ID!) {
  user(id: $x) {
    id
  }
}
```

### Use Descriptive Variable Names

```graphql
# Good
query SearchPosts(
  $searchQuery: String!
  $authorId: ID
  $publishedAfter: DateTime
  $maxResults: Int = 20
) {
  searchPosts(
    query: $searchQuery
    author: $authorId
    after: $publishedAfter
    first: $maxResults
  ) {
    # ...
  }
}

# Bad
query SearchPosts($q: String!, $a: ID, $d: DateTime, $n: Int) {
  # ...
}
```

### Group Related Variables

```typescript
// Good: Variables object mirrors input structure
const variables = {
  input: {
    title: formData.title,
    content: formData.content,
    tags: formData.tags,
  },
};

// Less clear: Flat variables
const variables = {
  title: formData.title,
  content: formData.content,
  tags: formData.tags,
};
```

### Validate Variables Client-Side

```typescript
function createPost(input: CreatePostInput) {
  // Validate before sending
  if (!input.title?.trim()) {
    throw new Error("Title is required");
  }
  if (input.title.length > 200) {
    throw new Error("Title too long");
  }

  return client.mutate({
    mutation: CREATE_POST,
    variables: { input },
  });
}
```

### Type Variables with TypeScript

```typescript
// Generated types from schema
interface GetUserQueryVariables {
  id: string;
}

// Use with Apollo Client
const { data } = useQuery<GetUserQuery, GetUserQueryVariables>(GET_USER, {
  variables: { id: userId }, // Type-checked
});
```

```

### references/tooling.md

```markdown
# Tooling

This reference covers tools for working with GraphQL operations, including code generation and linting.

## Table of Contents

- [GraphQL Code Generator](#graphql-code-generator)
- [ESLint GraphQL](#eslint-graphql)
- [IDE Extensions](#ide-extensions)
- [Operation Validation](#operation-validation)

## GraphQL Code Generator

### Overview

GraphQL Code Generator generates TypeScript types from your schema and operations, ensuring type safety throughout your application.

### Installation

```bash
npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typed-document-node
```

### Basic Configuration

Create `codegen.ts`:

```typescript
// codegen.ts
import { CodegenConfig } from "@graphql-codegen/cli";

const config: CodegenConfig = {
  overwrite: true,
  schema: "<URL_OF_YOUR_GRAPHQL_API>",
  // This assumes that all your source files are in a top-level `src/` directory - you might need to adjust this to your file structure
  documents: ["src/**/*.{ts,tsx}"],
  // Don't exit with non-zero status when there are no documents
  ignoreNoDocuments: true,
  generates: {
    // Use a path that works the best for the structure of your application
    "./src/types/__generated__/graphql.ts": {
      plugins: ["typescript", "typescript-operations", "typed-document-node"],
      config: {
        avoidOptionals: {
          // Use `null` for nullable fields instead of optionals
          field: true,
          // Allow nullable input fields to remain unspecified
          inputValue: false,
        },
        // Use `unknown` instead of `any` for unconfigured scalars
        defaultScalarType: "unknown",
        // Apollo Client always includes `__typename` fields
        nonOptionalTypename: true,
        // Apollo Client doesn't add the `__typename` field to root types so
        // don't generate a type for the `__typename` for root operation types.
        skipTypeNameForRoot: true,
      },
    },
  },
};

export default config;
```

### Run Generation

```bash
# One-time generation
npx graphql-codegen

# Watch mode for development
npx graphql-codegen --watch
```

### Package Scripts

```json
{
  "scripts": {
    "codegen": "graphql-codegen",
    "codegen:watch": "graphql-codegen --watch"
  }
}
```

### Generated Types Usage

```tsx
// Before: Manual typing
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
    }
  }
`;

// Manually typed
interface GetUserData {
  user: {
    id: string;
    name: string;
  } | null;
}

const { data } = useQuery<GetUserData>(GET_USER, { variables: { id } });

// After: Generated types
import { useGetUserQuery } from "./generated/graphql";

const { data } = useGetUserQuery({ variables: { id } });
// data.user is fully typed!
```

### Near-Operation File Generation

Generate types next to operations:

```typescript
const config: CodegenConfig = {
  schema: "http://localhost:4000/graphql",
  documents: ["src/**/*.graphql"],
  generates: {
    "src/": {
      preset: "near-operation-file",
      presetConfig: {
        extension: ".generated.ts",
        baseTypesPath: "generated/graphql.ts",
      },
      plugins: ["typescript-operations", "typescript-react-apollo"],
    },
    "src/generated/graphql.ts": {
      plugins: ["typescript"],
    },
  },
};
```

Results in:

```
src/
  components/
    UserCard/
      UserCard.graphql
      UserCard.generated.ts  # Generated types for this file
```

### Fragment Types

```tsx
// UserAvatar.graphql
// fragment UserAvatar on User {
//   id
//   name
//   avatarUrl
// }

import { UserAvatarFragment } from "./UserAvatar.generated";

interface UserAvatarProps {
  user: UserAvatarFragment;
}

export function UserAvatar({ user }: UserAvatarProps) {
  return <img src={user.avatarUrl} alt={user.name} />;
}
```

## ESLint GraphQL

### Installation

```bash
npm install -D @graphql-eslint/eslint-plugin
```

### Configuration

```javascript
// eslint.config.js (flat config)
import graphqlPlugin from "@graphql-eslint/eslint-plugin";

export default [
  {
    files: ["**/*.graphql"],
    languageOptions: {
      parser: graphqlPlugin.parser,
    },
    plugins: {
      "@graphql-eslint": graphqlPlugin,
    },
    rules: {
      "@graphql-eslint/known-type-names": "error",
      "@graphql-eslint/no-anonymous-operations": "error",
      "@graphql-eslint/no-duplicate-fields": "error",
      "@graphql-eslint/naming-convention": [
        "error",
        {
          OperationDefinition: {
            style: "PascalCase",
            forbiddenPrefixes: ["Query", "Mutation", "Subscription"],
          },
          FragmentDefinition: {
            style: "PascalCase",
          },
        },
      ],
    },
  },
];
```

### Recommended Rules

```javascript
rules: {
  // Syntax and validity
  '@graphql-eslint/known-type-names': 'error',
  '@graphql-eslint/known-fragment-names': 'error',
  '@graphql-eslint/no-undefined-variables': 'error',
  '@graphql-eslint/no-unused-variables': 'error',
  '@graphql-eslint/no-unused-fragments': 'error',
  '@graphql-eslint/unique-operation-name': 'error',
  '@graphql-eslint/unique-fragment-name': 'error',

  // Best practices
  '@graphql-eslint/no-anonymous-operations': 'error',
  '@graphql-eslint/no-duplicate-fields': 'error',
  '@graphql-eslint/require-id-when-available': 'warn',

  // Naming
  '@graphql-eslint/naming-convention': ['error', { ... }],
}
```

### Schema-Aware Rules

Provide schema for advanced validation:

```javascript
{
  files: ['**/*.graphql'],
  languageOptions: {
    parser: graphqlPlugin.parser,
    parserOptions: {
      schema: './schema.graphql',
      // or
      schema: 'http://localhost:4000/graphql',
    },
  },
}
```

## IDE Extensions

### VS Code

**GraphQL: Language Feature Support** (GraphQL Foundation)

- Syntax highlighting
- Autocomplete for schema types
- Go to definition
- Hover documentation
- Validation against schema

Configuration (`.graphqlrc.yml`):

```yaml
schema: "http://localhost:4000/graphql"
documents: "src/**/*.{graphql,ts,tsx}"
```

**Apollo GraphQL** (Apollo)

- Apollo-specific features
- Schema registry integration
- Performance insights

### JetBrains IDEs

**GraphQL** plugin:

- Syntax highlighting
- Schema-aware completion
- Validation
- Navigate to definition

Configuration (`.graphqlconfig`):

```json
{
  "schemaPath": "./schema.graphql",
  "includes": ["src/**/*.graphql"]
}
```

### Configuration Files

Common configuration file names:

- `.graphqlrc` (JSON)
- `.graphqlrc.yml` (YAML)
- `.graphqlrc.json` (JSON)
- `graphql.config.js` (JavaScript)

```yaml
# .graphqlrc.yml
schema: "http://localhost:4000/graphql"
documents: "src/**/*.graphql"
extensions:
  codegen:
    generates:
      ./src/generated/graphql.ts:
        plugins:
          - typescript
          - typescript-operations
```

## Operation Validation

### Validate Against Schema

```bash
# Using graphql-inspector
npx graphql-inspector validate ./src/**/*.graphql ./schema.graphql
```

### CI Integration

```yaml
# .github/workflows/graphql.yml
name: GraphQL Validation

on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Download schema
        run: npx graphql-inspector introspect http://localhost:4000/graphql --write schema.graphql

      - name: Validate operations
        run: npx graphql-inspector validate './src/**/*.graphql' schema.graphql

      - name: Check for breaking changes
        run: npx graphql-inspector diff schema.graphql http://localhost:4000/graphql
```

### Pre-commit Hook

```json
// package.json
{
  "lint-staged": {
    "*.graphql": ["eslint --fix", "graphql-inspector validate ./schema.graphql"]
  }
}
```

### Operation Complexity Check

```bash
# Check query complexity
npx graphql-query-complexity-checker \
  --schema ./schema.graphql \
  --query ./src/queries/GetUser.graphql \
  --max-complexity 100
```

### Persisted Queries Extraction

Generate persisted queries for production:

```typescript
// codegen.ts
const config: CodegenConfig = {
  generates: {
    "./persisted-queries.json": {
      plugins: ["graphql-codegen-persisted-query-ids"],
      config: {
        output: "client",
        algorithm: "sha256",
      },
    },
  },
};
```

Output:

```json
{
  "abc123...": "query GetUser($id: ID!) { user(id: $id) { id name } }",
  "def456...": "mutation CreatePost($input: CreatePostInput!) { ... }"
}
```

```