Prepared Docs

Prepared Docs

⌘K

    Loading navigation…

User

  1. Working With Data
  2. Graphql Development

GraphQL Development Workflow

Working with code generation, debugging, and troubleshooting

This guide covers the day-to-day development workflow when working with GraphQL, including code generation, debugging, and troubleshooting common issues.

Development Setup

Running Code Generation

Codegen runs automatically when you start development:

When you save a .graphql file, codegen automatically:

  1. Detects the change
  2. Regenerates the .graphql.ts file
  3. TypeScript recompiles with new types
  4. Your IDE updates with new type information

For manual generation (rarely needed):

Working with Schema Changes

Updating the Schema

When the backend GraphQL schema changes:

  1. Automatic: If running turbo run dev, the schema updates automatically
  2. Manual: Run turbo run codegen to pull latest schema

The schema is fetched from:

  • Local development: http://localhost:3000/graphql (This will be changed to not need the API running locally soon)

Schema Introspection

To manually introspect and save the schema:

This updates:

  • src/generated/schema.graphql - Human-readable schema
  • src/generated/graphql.introspection.json - For tooling

Generated Files

What Gets Generated

For each .graphql file, codegen creates a .graphql.ts file:

Generated File Contents

  1. Typed Document Nodes: For use with Apollo Client
  2. Query/Mutation Types: Full TypeScript types for results
  3. Variable Types: Type-safe variable interfaces
  4. Fragment Types: Reusable fragment type definitions

Git Configuration

Generated files are not committed to the repository:

This prevents:

  • Merge conflicts from generated code
  • Repository bloat
  • Outdated types if regeneration fails

Debugging GraphQL

TypeScript Type Issues

Problem: Types not updating after schema change

IntelliSense Not Working

If your IDE isn't showing proper types:

  1. Ensure codegen has run: Check for .graphql.ts files
  2. Restart TypeScript server:
    • VS Code: Cmd+Shift+P → "TypeScript: Restart TS Server"
    • Other IDEs: Check TypeScript service restart option
  3. Check imports: Ensure importing from .graphql (Vite resolves to .graphql.ts automatically)

Query Not Found

Error: Cannot find GetChatroomsDocument

Pre-commit Validation

Recommended: Enable Git Hooks

The easiest way to catch issues before committing is to enable pre-commit hooks:

This configures git to automatically run linting and formatting checks before each commit. The hooks will run the necessary validation commands for you.

Manual Validation

If you prefer to run checks manually, or if hooks aren't enabled:

ESLint Rules

Our GraphQL ESLint configuration helps maintain code quality by enforcing best practices and catching potential issues early. The rules are configured in turbo/packages/toolchain/eslint.config.base.mjs and apply to all .graphql files.

Enabled Rules

  • operations-recommended preset from @graphql-eslint/eslint-plugin - Includes essential rules:
    • no-anonymous-operations - Ensures all operations are named (better for debugging and logging)
    • known-type-names - Validates that all referenced types exist in the schema
    • known-argument-names - Ensures all arguments are defined by their fields
    • known-fragment-names - Validates fragment references
    • lone-anonymous-operation - Prevents mixing anonymous and named operations
    • @graphql-eslint/no-unused-variables - Error if you declare variables but don't use them
    • and more: https://the-guild.dev/graphql/eslint/rules

Common Rule Violations and Fixes

@graphql-eslint/selection-set-depth

What it does: Limits the depth of nested field selections to prevent overly complex queries that can impact performance.

When to disable: Legitimate deep queries (e.g., audit logs, detailed reports, complex nested data structures).

How to fix:

  • Review if queries can be flattened or split into multiple queries
  • Consider if all nested fields are actually needed
  • If the depth is necessary, discuss adjusting the rule threshold with the team

Example:

@graphql-eslint/require-selections

What it does: Ensures fragments and operations have an id field selected if available. This is required for Apollo Client caching to work properly.

When to disable: Id isn't the index field for the object.

How to fix:

  • Add the id field to the fragment/operation

Example:

@graphql-eslint/no-deprecated

What it does: Warns when using deprecated fields or types that are scheduled for removal.

When to disable: Legacy code using deprecated fields that need migration to newer alternatives.

How to fix:

  • Priority: Plan migration to non-deprecated alternatives
  • Check the GraphQL schema for deprecation notes to understand the replacement
  • Coordinate with backend team if deprecations are being removed
  • Create tickets to track migration work

Example:

@graphql-eslint/no-duplicate-fields

What it does: Prevents selecting the same field multiple times in a selection set.

How to fix: Remove duplicate field selections - this is usually a simple fix.

Example:

@graphql-eslint/naming-convention

What it does: Enforces consistent naming conventions for operations, types, and fields.

How to fix: Rename operations/types to match our 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)

Example:

Working with Suppressions

Reviewing Suppressions

When working in files with suppressions:

  1. Check if suppressions are still needed - Review the suppressed rule and see if the violation can be fixed
  2. Fix violations when possible - Address rule violations when you touch related code
  3. Prune obsolete suppressions - After fixing violations, run:

This automatically removes suppressions for violations that have been fixed.

Best Practices

✅ Do:

  • Follow the rules - ESLint will catch issues as you write GraphQL operations
  • Use fragments - They help with both organization and avoiding depth issues
  • Review suppressions - When working in files with suppressions, check if they're still needed
  • Fix violations incrementally - Address rule violations when you touch related code

❌ Don't:

  • Don't disable rules without reason - Suppressions should be temporary
  • Don't ignore violations - Rules catch real issues that can impact performance and maintainability

Checking for Violations

Check for GraphQL ESLint violations:

Run linting for affected packages only:

Prune obsolete suppressions:

Related Documentation

  • Operations Guide - Writing queries, mutations, subscriptions
  • Fragments Guide - Using fragments (includes ESLint examples)
  • ESLint Config: turbo/packages/toolchain/eslint.config.base.mjs
  • Suppressions: turbo/apps/dispatch/eslint-suppressions.json

Troubleshooting Common Issues

Issue: "Cannot find module './queries.graphql'"

Cause: Codegen hasn't run yet

Solution:

Issue: "Type 'ChatroomStatus' is not assignable to type 'string'"

Cause: Using string instead of enum

Solution:

Issue: "Fragment 'UserData' not found"

Cause: Missing fragment import in GraphQL file

Solution:

Issue: Slow Codegen Performance

Cause: Processing too many files

Solutions:

  1. Ensure documents glob pattern is specific
  2. Exclude generated files from search
  3. Use more specific paths in codegen config

Mocking GraphQL for Tests

Importing Factory Functions and Document Nodes

Factory functions are exported from @prepared911/data-gql/factories, and document nodes are exported from @prepared911/data-gql:

Basic Usage

Factory functions accept an optional overrides parameter to customize specific fields:

Using with MockedProvider

The most common use case is mocking GraphQL queries in component tests. Use renderWithMockedProvider from ~src/tests instead of manually setting up MockedProvider:

Testing Custom Hooks

For testing custom hooks that use Apollo Client hooks (useQuery, useMutation, useSubscription), use renderHook from @testing-library/react with WithMockedProvider as the wrapper:

Key points for hook testing:

  • WithMockedProvider is a function that returns a component wrapper: wrapper: WithMockedProvider({ mocks, addTypename: false })
  • Mock structure works identically to component tests - use factories and document nodes the same way

Overriding Specific Fields

You can override any field, including nested relationships:

Working with Fragments

Factory functions work seamlessly with fragment types. When you use a fragment in a query, you can create mock data that matches the fragment structure:

Working with Query Results

When mocking query results, structure your mock data to match the query's return type:

Note: The test setup uses a fixed Faker seed (faker.seed(0)), which means factory-generated mock data will be consistent across test runs. When you don't override specific fields, the factories will generate the same random values every time, making your tests more predictable and easier to debug.

Mocking Mutations

Mutations work the same way as queries. Use factories for mutation payload types:

Mocking Subscriptions

Subscriptions can be mocked the same way as queries and mutations:

Best Practices

✅ Do

  • Use factories for all mock data: They provide type safety and consistent defaults
  • Override only necessary fields: Let factories generate defaults for fields you don't care about
  • Match query structure: Ensure mock data structure matches your GraphQL query response
  • Use renderWithMockedProvider: Use the helper from ~src/tests instead of manually setting up MockedProvider
  • Use factories for payload types: Use factories like aChatroomRequestMediaDownloadPayload for mutation/subscription payloads
  • Mock subscriptions the same way: Subscriptions work identically to queries and mutations in mocks

❌ Don't

  • Don't manually create mock objects: Use factories instead of manually typing out mock data
  • Don't ignore type errors: If TypeScript complains, your mock structure doesn't match the query
  • Don't forget __typename: The factories add this automatically, but ensure addTypename is set correctly in MockedProvider
  • Don't create overly complex mocks: Only mock the data your test actually needs
  • Don't mock GraphQL hooks directly: Remove vi.mock("@prepared911/data-gql") and use typed-document-node mocks instead

Example: Complete Test Setup

Here's a complete example showing how to use factories in a real test with multiple operations and additional providers:

Key points from this example:

  • Import document nodes from @prepared911/data-gql (not from .graphql files)
  • Use factories for payload types (e.g., aChatroomRequestMediaDownloadPayload)
  • Use expect.any(String) or expect.any(Object) for variable matching when exact values aren't needed
  • Use renderWithMockedProvider with additional providers array
  • Mock non-GraphQL hooks separately (they don't need typed-document-node mocks)

Helper Functions

The codebase includes helper functions that combine factories with MockedProvider. Import from ~src/tests:

Common additional providers:

  • WithUiCoreProviders - Provides theme, resize observer, and base providers
  • ModalContextProvider - Provides modal context
  • Custom providers as needed

The renderWithMockedProvider helper automatically:

  • Sets up MockedProvider with addTypename={false}
  • Wraps with any additional providers you pass in (such as base providers like theme, translation, etc.)
  • Allows additional providers to be passed in reverse order (outermost first)

Related Documentation

  • Operations Guide - Writing queries and mutations
  • Fragments Guide - Using fragments for type-safe data sharing
  • Apollo Client Testing - Official Apollo testing documentation

Development Tips

1. Use GraphQL Extensions

Install GraphQL extensions for your IDE:

  • VS Code: Apollo GraphQL - Provides syntax highlighting, autocomplete, and validation for GraphQL files
  • IntelliJ: GraphQL plugin

2. Apollo Client Devtools

Install browser extension for debugging:

  • View cache contents
  • Inspect active queries
  • Test mutations manually

3. Type-First Development

  1. Write your GraphQL operation first
  2. Codegen automatically generates types (or run turbo run codegen manually)
  3. Use generated types in components
  4. Let TypeScript guide your implementation

4. Fragment Organization

Keep fragments close to their consumers:

5. Debugging Network Requests

Use Apollo Client's built-in logging:

Performance Considerations

Codegen Performance

To improve codegen speed:

  1. Specific globs: Use precise file patterns
  2. Exclude tests: Don't process test files
  3. Parallel generation: Codegen runs in parallel by default

Runtime Performance

  1. Use fragments: Prevents over-fetching
  2. Avoid deeply nested queries: Keep queries shallow and focused. Excessive nesting can lead to performance issues on both the client and server. See Depth limiting

Environment-Specific Configuration

Local Development

CI Environment

Summary

This guide covered the practical aspects of working with GraphQL in the Prepared monorepo. Here's a quick reference of key concepts:

Key Workflows

  • Code Generation: Runs automatically during turbo run dev, or manually with turbo run codegen
  • File Organization: Use colocation to keep .graphql files next to components
  • Type Safety: Import from .graphql files (Vite resolves to .graphql.ts automatically)
  • Debugging: Use Apollo DevTools, check generated files, restart TypeScript server

Quick Reference

  • Generate types: turbo run codegen (usually automatic during turbo run dev)
  • Check for generated files: Look for .graphql.ts files next to .graphql files
  • Fix IntelliSense: Restart TypeScript server in your IDE
  • Troubleshoot imports: Ensure codegen has run and files exist

Related Documentation

  • Operations Guide - Writing queries, mutations, subscriptions
  • Colocation Patterns - Organizing GraphQL files
  • Using Fragments - Type-safe data sharing

Additional Resources

  • Review Apollo Client documentation for advanced patterns
  • Learn about GraphQL best practices
  • Explore Apollo's developer tools

Previous

Working with data / GraphQL Fragments

Next

Working with data / Local Storage

On this page

Development Setup
Running Code Generation
Working with Schema Changes
Updating the Schema
Schema Introspection
Generated Files
What Gets Generated
Generated File Contents
Git Configuration
Debugging GraphQL
TypeScript Type Issues
IntelliSense Not Working
Query Not Found
Pre-commit Validation
Recommended: Enable Git Hooks
Manual Validation
ESLint Rules
Enabled Rules
Common Rule Violations and Fixes
@graphql-eslint/selection-set-depth
@graphql-eslint/require-selections
@graphql-eslint/no-deprecated
@graphql-eslint/no-duplicate-fields
@graphql-eslint/naming-convention
Working with Suppressions
Reviewing Suppressions
Best Practices
Checking for Violations
Related Documentation
Troubleshooting Common Issues
Issue: "Cannot find module './queries.graphql'"
Issue: "Type 'ChatroomStatus' is not assignable to type 'string'"
Issue: "Fragment 'UserData' not found"
Issue: Slow Codegen Performance
Mocking GraphQL for Tests
Importing Factory Functions and Document Nodes
Basic Usage
Using with MockedProvider
Testing Custom Hooks
Overriding Specific Fields
Working with Fragments
Working with Query Results
Mocking Mutations
Mocking Subscriptions
Best Practices
✅ Do
❌ Don't
Example: Complete Test Setup
Helper Functions
Related Documentation
Development Tips
1. Use GraphQL Extensions
2. Apollo Client Devtools
3. Type-First Development
4. Fragment Organization
5. Debugging Network Requests
Performance Considerations
Codegen Performance
Runtime Performance
Environment-Specific Configuration
Local Development
CI Environment
Summary
Key Workflows
Quick Reference
Related Documentation
Additional Resources