Writing queries, mutations, and subscriptions with typed document nodes
Operations are how you interact with the GraphQL API. We use typed document nodes instead of auto-generated hooks, providing better flexibility and type safety.
Now that you understand the architecture and key concepts, let's learn how to write GraphQL operations. This section covers queries, mutations, and subscriptions—the core ways you interact with the GraphQL API.
Queries
Basic Query Structure
Using in Components
Before and After: Hooks vs Document Nodes
❌ Don't do this - Auto-generated Hooks
Old Pattern: Using generated hooks
Problems:
Less flexible than standard Apollo hooks
Harder to mock in tests
Couples you to specific codegen output
Often uses magic strings
✅ Do this - Typed Document Nodes
New Pattern: Using document nodes with useQuery
Benefits:
All Apollo Client options available
Easier to test with MockedProvider
Better IDE support and debugging
Type-safe variables with enums
Working with Enums
GraphQL enums are generated as TypeScript const enums, providing type safety for all enum values.
Using Generated Enums
❌ Don't do this - Magic Strings
✅ Do this - Type-Safe Enums
Enum Benefits with enumsAsConst
Our codegen configuration uses enumsAsConst: true, which generates:
This provides:
Tree-shakeable code (only used values are bundled)
Better TypeScript inference
Autocomplete in IDEs
Compile-time validation
Mutations
Basic Mutation
Using Mutations
Field Selection Best Practices
❌ Don't do this - Over-fetching
✅ Do this - Request Only What You Need
❌ Don't do this - Query Type Extractions
Problem: Extracting types from query results using TypeScript indexed access
Partial improvement: You can use NonNullable to handle nullability (NonNullable<Query["field"]>["subfield"]), but this still has the same limitations. See the Fragments guide for the full migration path, including the null-safe extraction pattern and the final fragment-based solution.
Variable Usage
❌ Don't do this - Unused Variables
ESLint will warn: @graphql-eslint/no-unused-variables
✅ Do this - Use All Declared Variables
Subscriptions
Basic Subscription
Using Subscriptions
Error Handling
Query Error Handling
Mutation Error Handling
Operation Naming Conventions
All operations must be named for better debugging and tooling:
Naming conventions:
Queries: Start with Get or describe the data (e.g., GetUser, SearchIncidents)
Mutations: Start with a verb (e.g., UpdateChatroom, CreateIncident)
Subscriptions: Start with On (e.g., OnChatroomUpdate, OnMessageReceived)
Optimistic Updates
For better UX, use optimistic updates with mutations:
Best Practices Summary
✅ Do
Use typed document nodes with standard Apollo hooks
Import and use generated enum types
Name all operations descriptively
Request only the fields you need
Handle errors appropriately
Use fragments for reusable selections
❌ Don't
Use auto-generated hooks from old patterns
Hard-code enum values as strings
Over-fetch data you don't use
Define variables you don't use
Use anonymous operations
Ignore TypeScript errors on variables
Once you're comfortable writing operations, the next section covers where to organize them using the colocation pattern.