GraphQL Operations Developer
Overview
Guide the creation and maintenance of GraphQL operations following project conventions for type safety, organization, error handling, and testing with gql.tada and urql.
When to Use
-
Creating new GraphQL queries or mutations
-
Integrating with Saleor API endpoints
-
Updating schema after Saleor changes
-
Working with urql client configuration
-
Creating MSW mocks for testing
Quick Reference
Tool Purpose
gql.tada Type-safe GraphQL with TypeScript inference
urql GraphQL client with caching
@urql/exchange-auth Authentication (Bearer token)
@urql/exchange-retry Rate limit retry (429, max 5 attempts)
File Organization
src/lib/graphql/ ├── client.ts # urql client configuration ├── operations/ # GraphQL operation definitions ├── fragments/ # Reusable GraphQL fragments ├── mocks/ # MSW test mocks └── schema.graphql # Saleor schema (generated)
src/modules/<entity>/ ├── repository.ts # Uses GraphQL operations └── ...
Creating Operations
Use gql.tada for all operations. Types are inferred from schema automatically:
import { graphql } from 'gql.tada';
export const GetCategoriesQuery = graphql( query GetCategories($first: Int!) { categories(first: $first) { edges { node { id, name, slug, description } } } });
// Type inferred automatically type GetCategoriesResult = ResultOf<typeof GetCategoriesQuery>;
For shared fields, extract fragments and pass as second argument to graphql() .
Repository Pattern
Each entity has a repository class that encapsulates GraphQL operations and maps responses to domain models:
export class CategoryRepository { constructor(private readonly client: Client) {}
async findAll(): Promise<Category[]> { const result = await this.client.query(GetCategoriesQuery, { first: 100 }); if (result.error) { throw GraphQLError.fromCombinedError(result.error, 'GetCategories'); } return this.mapCategories(result.data?.categories); }
async create(input: CategoryInput): Promise<Category> { const result = await this.client.mutation(CreateCategoryMutation, { input }); if (result.error) { throw GraphQLError.fromCombinedError(result.error, 'CreateCategory'); } if (result.data?.categoryCreate?.errors?.length) { throw new GraphQLError('Category creation failed', result.data.categoryCreate.errors); } return this.mapCategory(result.data?.categoryCreate?.category); } }
Key pattern: Always map GraphQL responses to domain models in the repository. Never expose GraphQL types to services.
Error Handling
Two error types to always check:
-
Network/GraphQL errors (result.error ): Wrap with GraphQLError.fromCombinedError(error, 'OperationName', { context })
-
Mutation validation errors (result.data?.mutation?.errors ): Check array length, throw with field details
See references/error-handling.md for complete error patterns, classification, and MSW error mocking.
Schema Management
pnpm fetch-schema # Updates schema.graphql and graphql-env.d.ts
Update schema when: new Saleor features needed, after Saleor version upgrade, or when encountering schema drift errors. Always commit schema changes with the feature implementation.
Testing
Mock GraphQL operations with MSW using graphql.query() and graphql.mutation() handlers. See analyzing-test-coverage skill for full MSW setup patterns.
Client Configuration
The urql client is configured in src/lib/graphql/client.ts with: cacheExchange , authExchange (Bearer token), retryExchange (1s-15s backoff, 5 attempts, retries on 429 and network errors), and fetchExchange .
Best Practices
Do:
-
Use gql.tada for all operations (automatic type inference)
-
Keep operations close to their domain modules
-
Map GraphQL responses to domain models in repository
-
Include operation name in error context
-
Update mocks when schema changes
-
Extract shared fields to fragments
Don't:
-
Use raw string queries (no type safety)
-
Expose GraphQL types directly to services
-
Skip error handling for any operation
-
Hardcode pagination limits (use constants)
Validation Checkpoints
Phase Validate Command
Schema fresh No drift pnpm fetch-schema
Operations typed gql.tada inference Check IDE types
Mocks match MSW handlers pnpm test
Error handling All paths covered Code review
Common Mistakes
Mistake Fix
Not checking errors array Always check result.data?.mutation?.errors
Exposing GraphQL types Map to domain types in repository
Missing error context Include operation name in errors
Stale schema Run pnpm fetch-schema after Saleor updates
Not using fragments Extract shared fields to fragments
External Documentation
For up-to-date library docs, use Context7 MCP:
-
urql: resolve-library-id with /urql-graphql/urql
-
gql.tada: resolve-library-id with "gql.tada"
References
Skill Reference Files
-
Error Handling - Error types, wrapping, classification, and MSW error mocking
-
Fragment Patterns - Fragment composition and type inference
Project Resources
-
src/lib/graphql/client.ts
-
Client configuration
-
src/lib/graphql/operations/
-
Existing operations
-
docs/CODE_QUALITY.md#graphql--external-integrations
-
Quality standards
Related Skills
-
Complete entity workflow: See adding-entity-types for full implementation including bulk mutations
-
Bulk operations: See adding-entity-types/references/bulk-mutations.md for chunking patterns
-
Testing GraphQL: See analyzing-test-coverage for MSW setup
Troubleshooting
Common Error Scenarios
Error Cause Fix
CombinedError: [Network]
API unreachable or URL malformed Verify --url ends with /graphql/ and instance is running
CombinedError: [GraphQL]
Invalid query or variables Run pnpm fetch-schema and check operation against schema
result.data?.mutation?.errors non-empty Saleor validation rejection Read field and message from errors array for specifics
TypeError: Cannot read property of undefined
Missing null check on response Always check result.data before accessing nested properties
HTTP 429 (rate limited) Too many requests Built-in retry exchange handles this; increase delay if persistent
Debugging Steps
-
Check schema freshness: pnpm fetch-schema — ensures local schema matches remote
-
Isolate the operation: Test the query/mutation in Saleor's GraphQL Playground first
-
Add error context: Include operation name in all error wrapping calls
-
Check MSW handlers: Ensure test mocks match updated operation signatures
-
Verify type inference: Hover over ResultOf<typeof Query> in IDE to confirm types
Quick Reference Rule
For a condensed quick reference, see .claude/rules/graphql-patterns.md (automatically loaded when editing GraphQL operations and repository files).