Tipando APIs GraphQL com código gerado

1. Por que gerar tipos para GraphQL?

Um dos maiores desafios ao trabalhar com GraphQL em TypeScript é a natureza dinâmica do schema. Diferente de APIs REST, onde as respostas são geralmente estáticas e previsíveis, o GraphQL permite que o cliente especifique exatamente quais campos deseja. Isso significa que o tipo de retorno de uma query pode variar drasticamente dependendo da seleção de campos.

Tipar manualmente essas variações é tedioso, propenso a erros e dificulta a manutenção. Imagine ter que definir manualmente interfaces para cada combinação possível de campos em uma query com 20 campos opcionais. A geração automática de tipos resolve esse problema, oferecendo:

  • Segurança em tempo de compilação: erros de tipo são capturados antes de chegar à produção
  • Autocomplete inteligente: seu editor sabe exatamente quais campos estão disponíveis
  • Refatoração segura: mudanças no schema são propagadas automaticamente para todos os consumidores

2. Ferramentas de geração de código: GraphQL Code Generator

A ferramenta mais madura para esse fim é o GraphQL Code Generator (graphql-codegen). Para começar, instale os pacotes necessários:

npm install graphql
npm install -D @graphql/codegen-cli @graphql-codegen/typescript

Crie um arquivo de configuração codegen.ts na raiz do projeto:

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

const config: CodegenConfig = {
  schema: 'https://api.example.com/graphql',
  documents: ['src/**/*.graphql', 'src/**/*.tsx'],
  generates: {
    './src/generated/graphql.ts': {
      plugins: ['typescript', 'typescript-operations']
    }
  }
};

export default config;

Execute o gerador:

npx graphql-codegen

Isso gerará automaticamente tipos TypeScript baseados no schema GraphQL remoto e nos documentos (queries, mutations) que você definiu.

3. Gerando tipos para operações

Suponha que você tenha o seguinte schema GraphQL:

type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Query {
  getUser(id: ID!): User
}

E uma query definida em src/queries/getUser.graphql:

query getUser($id: ID!) {
  getUser(id: $id) {
    id
    name
    email
  }
}

Após executar o codegen, você terá tipos como:

export type GetUserQueryVariables = {
  id: Scalars['ID'];
};

export type GetUserQuery = {
  getUser?: {
    id: string;
    name: string;
    email: string;
  } | null;
};

Para usar com inferência automática, utilize TypedDocumentNode:

import { TypedDocumentNode } from '@graphql-typed-document-node/core';
import { GetUserQuery, GetUserQueryVariables } from './generated/graphql';

const getUserDocument: TypedDocumentNode<GetUserQuery, GetUserQueryVariables> = gql`
  query getUser($id: ID!) {
    getUser(id: $id) {
      id
      name
      email
    }
  }
`;

4. Plugins específicos para React e hooks

Para projetos React, plugins especializados geram hooks tipados automaticamente. Instale:

npm install -D @graphql-codegen/typescript-react-query

Atualize a configuração:

const config: CodegenConfig = {
  schema: 'https://api.example.com/graphql',
  documents: ['src/**/*.graphql'],
  generates: {
    './src/generated/graphql.ts': {
      plugins: [
        'typescript',
        'typescript-operations',
        'typescript-react-query'
      ],
      config: {
        fetcher: 'fetch'
      }
    }
  }
};

Agora você pode usar hooks tipados:

import { useGetUserQuery } from './generated/graphql';

function UserProfile({ userId }: { userId: string }) {
  const { data, isLoading, error } = useGetUserQuery({
    variables: { id: userId }
  });

  if (isLoading) return <div>Carregando...</div>;
  if (error) return <div>Erro: {error.message}</div>;

  return <h1>{data?.getUser?.name}</h1>;
}

O hook useGetUserQuery já sabe que data.getUser tem os campos id, name e email, com tipos corretos.

5. Tipando fragmentos e composição de queries

Fragmentos são essenciais para reutilização em GraphQL. Com codegen, eles geram tipos específicos:

fragment UserFields on User {
  id
  name
  email
}

query getUserWithPosts($id: ID!) {
  getUser(id: $id) {
    ...UserFields
    posts {
      title
    }
  }
}

O código gerado incluirá:

export type UserFieldsFragment = {
  id: string;
  name: string;
  email: string;
};

export type GetUserWithPostsQuery = {
  getUser?: UserFieldsFragment & {
    posts: Array<{ title: string }>;
  } | null;
};

Isso permite composição segura: se você atualizar o fragmento UserFields, todas as queries que o utilizam serão atualizadas automaticamente.

6. Lidando com schemas remotos e escalares customizados

GraphQL permite escalares customizados como Date, JSON ou UUID. Configure o mapeamento no codegen.ts:

const config: CodegenConfig = {
  schema: 'https://api.example.com/graphql',
  generates: {
    './src/generated/graphql.ts': {
      plugins: ['typescript', 'typescript-operations'],
      config: {
        scalars: {
          Date: 'string',
          JSON: 'Record<string, unknown>',
          UUID: 'string'
        }
      }
    }
  }
};

Para enums e unions, o codegen gera tipos discriminados automaticamente:

export type UserRole = 'ADMIN' | 'USER' | 'MODERATOR';

export type SearchResult = 
  | { __typename: 'User'; id: string; name: string }
  | { __typename: 'Post'; id: string; title: string };

7. Estratégias avançadas: watchers e pipelines de build

Ative o modo watch para regenerar tipos automaticamente durante o desenvolvimento:

npx graphql-codegen --watch

Integre a geração no pipeline de CI/CD:

# .github/workflows/ci.yml
- name: Generate GraphQL types
  run: npx graphql-codegen
- name: Type check
  run: npx tsc --noEmit

Existem duas estratégias para versionamento:
- Gerar sob demanda: não versionar os arquivos gerados, executar codegen no build
- Versionar código gerado: commit dos arquivos para garantir consistência entre desenvolvedores

A escolha depende do seu fluxo de trabalho, mas para times grandes, recomenda-se versionar o código gerado para evitar surpresas.

8. Boas práticas e armadilhas comuns

Evite tipos genéricos demais: configure exactOptionalPropertyTypes no tsconfig.json para evitar que campos opcionais aceitem undefined indevidamente.

{
  "compilerOptions": {
    "exactOptionalPropertyTypes": true
  }
}

Gerencie __typename: por padrão, o codegen inclui __typename nos tipos. Se não precisar, configure:

config: {
  nonOptionalTypename: false,
  skipTypename: true
}

Cuidado com variáveis $: o codegen gera tipos para variáveis com prefixo $. Certifique-se de que suas queries usam a sintaxe correta.

Quando não gerar código: para projetos muito pequenos ou schemas extremamente voláteis (que mudam várias vezes ao dia), a geração automática pode ser mais custosa que benéfica. Nesses casos, considere tipagem manual ou ferramentas mais leves como graphql-zeus.

A geração de tipos para GraphQL com TypeScript não é apenas uma conveniência — é uma prática fundamental para manter a sanidade mental em projetos de médio e grande porte. Com ferramentas como o GraphQL Code Generator, você elimina uma classe inteira de bugs e acelera significativamente o desenvolvimento.

Referências