GraphQL Codegen: TypeScript & Schema Introspection Guide

by Aria Freeman 57 views

Hey guys! Today, we're diving deep into implementing GraphQL schema introspection and TypeScript code generation using GraphQL Codegen. This is super crucial for maintaining type safety and streamlining your development workflow, especially when dealing with GraphQL APIs like AWS AppSync. Let's get started!

Overview

In this comprehensive guide, we'll walk through the process of setting up an automated workflow that fetches your GraphQL schema from an AWS AppSync endpoint using introspection. We'll then store it locally and generate TypeScript types and interfaces using GraphQL Code Generator. This eliminates the need for manual type creation and ensures rock-solid type safety throughout your application. This is a game-changer for productivity and reducing those pesky runtime errors. If you're tired of manually creating types, then you're in the right place!

Why Automate GraphQL Type Generation?

Manually creating and maintaining TypeScript types for your GraphQL schema can be a real drag. It's time-consuming, error-prone, and frankly, not the best use of your valuable time. Automating this process with GraphQL Codegen offers several key advantages:

  • Reduced Boilerplate: Say goodbye to writing repetitive type definitions. Codegen handles it all for you.
  • Improved Type Safety: Ensure your codebase is type-safe, catching errors at compile time rather than runtime.
  • Enhanced Developer Experience: Spend less time on manual tasks and more time building features.
  • Consistent Types: Keep your types in sync with your schema, eliminating inconsistencies.
  • Increased Productivity: Focus on writing business logic, not type definitions.

What We'll Cover

We'll cover everything from setting up GraphQL Codegen to generating typed Apollo React hooks. By the end of this guide, you'll have a fully automated workflow that keeps your types up-to-date with your GraphQL schema. We will tackle head-on the most frequent pain points of developers who are still managing their types manually, highlighting how each step of this setup can significantly reduce development time and improve code quality.

Context

For context, we're currently using Apollo Client v3.13.9 with Clerk authentication (already configured). Our AppSync endpoint requires Bearer token authentication via Clerk JWT. The goal is to automate TypeScript type generation from the GraphQL schema, preferring introspection over manual schema management. We already have existing GraphQL mutations located in src/services/graphql/mutations/. This setup is typical for modern web applications leveraging GraphQL for data fetching and mutations, and the need for automation becomes even more critical as projects scale and schemas evolve.

Requirements

Here’s what we need to accomplish:

  • [ ] Set up GraphQL Code Generator with introspection support
  • [ ] Configure graphql.config.yml for the AppSync endpoint with authentication
  • [ ] Create codegen.ts configuration for TypeScript generation
  • [ ] Generate typed Apollo React hooks for queries/mutations
  • [ ] Store the introspected schema locally (schema.json)
  • [ ] Integrate with the existing Apollo Client setup
  • [ ] Add npm scripts for the codegen workflow
  • [ ] Configure environment variables for authentication

Each of these requirements is designed to streamline your development process, making it more efficient and less prone to errors. Let’s break down each of these requirements and understand why they are essential for a robust and maintainable GraphQL setup. We aim for a solution that integrates seamlessly with existing tools and processes, ensuring that type generation becomes an automated part of your workflow.

Technical Details

Package Dependencies

Here are the dev dependencies we'll be using:

{
  "devDependencies": {
    "@graphql-codegen/cli": "^5.0.7",
    "@graphql-codegen/introspection": "^4.0.3",
    "@graphql-codegen/typescript": "^4.6.1",
    "@graphql-codegen/typescript-operations": "^4.6.1",
    "@graphql-codegen/typescript-react-apollo": "^4.x.x",
    "graphql-config": "^5.1.5"
  }
}

These packages are the core of our type generation setup. @graphql-codegen/cli is the command-line interface for GraphQL Codegen, while @graphql-codegen/introspection allows us to fetch the schema from our AppSync endpoint. The @graphql-codegen/typescript family of plugins handles the actual TypeScript type generation, and graphql-config provides a standardized way to configure our GraphQL tooling.

Authentication Flow

Clerk provides JWT tokens via getToken({ template: '1day-token' }). This token is passed as a Bearer token in the Authorization header. We need a long-lived token for CI/CD and development codegen. This authentication setup is crucial for securing your GraphQL API and ensuring that only authorized users can access your data. Handling tokens correctly is vital for both development and production environments, as it impacts how you automate type generation and secure your application.

Existing Apollo Setup

Our auth link is located at src/lib/apollo/links/auth.ts, the Apollo Provider at src/lib/apollo/provider.tsx, and the HTTP link configured for AppSync at src/lib/apollo/links/http.ts. Knowing the structure of our existing Apollo setup is important for integrating the generated types and hooks seamlessly. This ensures that the new type generation workflow complements rather than disrupts the existing data fetching patterns in our application.

Implementation Plan

Let’s break down the implementation into manageable steps.

Step 1: Install Dependencies

First, let’s install the necessary packages:

yarn add -D @graphql-codegen/cli @graphql-codegen/introspection @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo graphql-config

This command adds all the required packages as development dependencies to your project. This ensures that these tools are available during development and build processes but are not included in the production bundle. It's a crucial first step in setting up the type generation workflow.

Step 2: Create graphql.config.yml

Next, create a graphql.config.yml file with the following configuration:

schema:
  - ${VITE_APPSYNC_GRAPHQL_URL}:
      headers:
        Authorization: Bearer ${CLERK_TEST_TOKEN}
documents:
  - src/**/*.graphql
  - src/**/*.{ts,tsx}
extensions:
  endpoints:
    default:
      url: ${VITE_APPSYNC_GRAPHQL_URL}
      headers:
        Authorization: Bearer ${CLERK_TEST_TOKEN}

This configuration tells GraphQL Codegen where to fetch the schema from (our AppSync endpoint) and how to authenticate. It also specifies the documents to scan for GraphQL operations. The ${VITE_APPSYNC_GRAPHQL_URL} and ${CLERK_TEST_TOKEN} are environment variables that hold your AppSync GraphQL URL and Clerk test token, respectively. Using environment variables is crucial for security and flexibility, allowing you to easily switch between different environments.

Step 3: Create codegen.ts

Now, let's create a codegen.ts file with the following configuration:

import type { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
  schema: [
    {
      [process.env.VITE_APPSYNC_GRAPHQL_URL]: {
        headers: {
          Authorization: `Bearer ${process.env.CLERK_TEST_TOKEN}`
        }
      }
    }
  ],
  documents: ['src/**/*.{ts,tsx}', 'src/**/*.graphql'],
  generates: {
    './schema.json': {
      plugins: ['introspection']
    },
    './src/types/generated/graphql.ts': {
      plugins: [
        'typescript',
        'typescript-operations',
        'typescript-react-apollo'
      ],
      config: {
        withHooks: true,
        withComponent: false,
        withHOC: false,
        scalars: {
          AWSDateTime: 'string',
          AWSJSON: 'string',
          AWSEmail: 'string'
        }
      }
    }
  }
}
export default config

This is the heart of our type generation setup. We configure the schema endpoint, documents to scan, and how to generate the types. We're using the introspection plugin to fetch the schema and the typescript, typescript-operations, and typescript-react-apollo plugins to generate TypeScript types and React Apollo hooks. We're also mapping AppSync-specific scalars like AWSDateTime, AWSJSON, and AWSEmail to string. This configuration is highly customizable, allowing you to fine-tune the type generation process to meet your specific needs.

Step 4: Add npm Scripts

Let's add some npm scripts to our package.json to make running Codegen easier:

{
  "scripts": {
    "codegen": "graphql-codegen",
    "codegen:watch": "graphql-codegen --watch",
    "type-check": "tsc --noEmit && yarn codegen"
  }
}

These scripts provide convenient commands for generating types, watching for changes, and running type checks. yarn codegen generates types once, yarn codegen:watch watches for changes and regenerates types automatically, and yarn type-check runs TypeScript type checking along with codegen. These scripts streamline the development process and make it easy to integrate type generation into your workflow.

Step 5: Configure Environment Variables

Add the following to your .env file:

CLERK_TEST_TOKEN=<long-lived-token-from-clerk>

Remember to replace <long-lived-token-from-clerk> with your actual Clerk test token. It's essential to keep sensitive information like tokens out of your codebase and use environment variables instead. This ensures that your tokens are not exposed in your repository and that you can easily manage different tokens for different environments.

Step 6: Update .gitignore

Finally, let's update our .gitignore file to exclude generated files:

schema.json
src/types/generated/

This prevents the generated schema and types from being committed to your repository, keeping your codebase clean and preventing potential conflicts. Generated files can always be regenerated, so there's no need to track them in your version control system.

Code Examples

Generated Hook Usage

Here’s how you can use the generated hooks:

// Before (manual typing)
const { data, loading } = useQuery(GET_USER_QUERY, {
  variables: { id: userId }
})

// After (generated typed hook)
import { useGetUserQuery } from '@/types/generated/graphql'

const { data, loading } = useGetUserQuery({
  variables: { id: userId }
})
// TypeScript knows data.user.name is a string

Notice how the generated hook provides type safety out of the box. TypeScript now knows the structure of the data returned by the query, allowing you to catch errors at compile time. This is a huge win for developer productivity and code quality.

Clerk Token Configuration

Here’s how to get a long-lived token for codegen:

// Get long-lived token for codegen (development/CI)
// 1. Go to Clerk Dashboard → JWT Templates
// 2. Create/update '1day-token' template
// 3. Generate token for test user: [email protected]
// 4. Add to .env as CLERK_TEST_TOKEN

This process ensures that you have a token that's valid for a longer period, which is essential for automated processes like codegen. Following these steps guarantees that your codegen process can authenticate with your AppSync endpoint without manual intervention.

Resources

Here are some useful resources:

These resources provide in-depth information about the tools and technologies we're using, allowing you to dive deeper into specific topics and troubleshoot any issues you might encounter.

Acceptance Criteria

We’ll know we’ve succeeded when:

  • [ ] The GraphQL schema is successfully fetched from the AppSync endpoint
  • [ ] The schema is stored locally as schema.json
  • [ ] TypeScript types are generated in src/types/generated/graphql.ts
  • [ ] Typed React hooks are generated for all queries/mutations
  • [ ] Integration works with the existing Apollo Client setup
  • [ ] The yarn codegen command runs successfully
  • [ ] Generated types have proper AppSync scalar mappings
  • [ ] CI/CD can run codegen with the test token
  • [ ] Documentation is updated with setup instructions

These acceptance criteria ensure that the type generation workflow is fully functional and integrated into your development process. Meeting these criteria guarantees that you have a robust and maintainable setup.

Notes for Developers

Here are some important notes:

  • Authentication: A Clerk JWT token is required for schema introspection.
  • AppSync Scalars: Custom scalars (AWSDateTime, etc.) are mapped to strings.
  • Introspection: May be disabled in production - use a dev endpoint if needed.
  • Token Management: Use environment variables and never commit tokens.
  • Watch Mode: Use yarn codegen:watch during development for auto-regeneration.
  • Type Safety: All GraphQL operations will have full TypeScript support.
  • Performance: Generated types improve IDE performance and catch errors at compile time.

These notes highlight key considerations for developers implementing this workflow, ensuring that you avoid common pitfalls and optimize your setup for performance and security.

Security Considerations

Finally, let's talk about security:

  • Never commit CLERK_TEST_TOKEN to the repository.
  • Use environment variables for all sensitive data.
  • Consider using AWS Secrets Manager for production tokens.
  • Introspection should be disabled in production AppSync.

Security is paramount, especially when dealing with sensitive information like tokens. Following these guidelines ensures that your application remains secure and compliant with best practices. This is a critical part of setting up any automated workflow.

Conclusion

So there you have it! Implementing GraphQL schema introspection and TypeScript code generation with GraphQL Codegen is a powerful way to streamline your development workflow and ensure type safety in your application. By following these steps, you'll be well on your way to building more robust and maintainable applications. Happy coding, guys!