{count, plural, one {Corso} other {Corsi}}
I tipi TypeScript scritti a mano per un'API GraphQL diventano obsoleti nel momento in cui qualcuno modifica lo schema.
Mettiamo che uno sviluppatore backend rinomini il campo title del tipo Course in name. Il frontend continua a compilare perché l'interfaccia Course scritta a mano non ha idea del cambiamento, quindi il bug emerge solo a runtime. Le applicazioni reali tendono ad avere dozzine di query, mutation e fragment, quindi è facile capire quanto rapidamente la situazione possa degenerare.
GraphQL Code Generator, o in breve GraphQL Codegen, legge il tuo schema e le operazioni GraphQL e le trasforma in tipi, helper per le query, integrazioni client e altro boilerplate che altrimenti scriveresti a mano. È usato soprattutto con TypeScript e client popolari come Apollo e urql. Un solo comando riporta i tuoi tipi in sincronia ogni volta che lo schema cambia.
In questo articolo ti mostrerò come configurare GraphQL Codegen in un piccolo progetto TypeScript, come impostarlo, usare i tipi generati ed eseguirlo automaticamente come parte del tuo workflow.
Come si confronta GraphQL con REST? Leggi la nostra Guida completa: GraphQL vs REST per conoscere differenze e vantaggi.
Che cos'è GraphQL Codegen?
GraphQL Codegen è uno strumento da riga di comando che trasforma le definizioni GraphQL in codice applicativo.
È un progetto open-source di The Guild e lo installi come dipendenza di sviluppo nel tuo progetto. Mette in relazione tre elementi:
- Schema GraphQL: Il contratto tra server e client, con tutti i tipi, i campi e gli argomenti esposti dall'API
- Operazioni GraphQL: Le query, le mutation, le subscription e i fragment che la tua applicazione invia all'API
- Codice generato: I tipi e gli helper che Codegen scrive in base allo schema e alle tue operazioni
Lo schema dice a Codegen cosa è possibile e le operazioni gli dicono cosa richiede la tua applicazione.
Codegen combina entrambi, così ogni tipo generato corrisponde esattamente alla forma della risposta per una specifica operazione. Verifica anche ogni operazione rispetto allo schema. Il campo title rinominato dall'introduzione fermerebbe la generazione con un errore come Cannot query field "title" on type "Course", permettendoti di intercettarlo prima dell'esecuzione del codice.
L'output dipende dai plugin e dalla configurazione. Codegen di per sé non scrive TypeScript. Lo fanno i plugin. Ad esempio, il plugin typescript genera i tipi di schema, e il plugin typescript-operations genera i tipi per le tue query e mutation. Esistono plugin anche per altri linguaggi, come Java e C#, ma in questo articolo restiamo su TypeScript.
Una cosa da ricordare è che Codegen non sostituisce il tuo server GraphQL.
Non risolve query né recupera dati. Legge le definizioni GraphQL durante lo sviluppo e scrive file. Il resto lo fanno il tuo server e il tuo client GraphQL.
Come funziona GraphQL Codegen
Ogni setup di Codegen segue lo stesso flusso: schema + operazioni GraphQL -> configurazione di Codegen -> codice generato.

Diagramma del workflow di GraphQL Codegen
L'idea generale è questa:
-
Fornisci uno schema: Indica a Codegen un file locale
.graphqlo un endpoint GraphQL in esecuzione, che Codegen legge tramite introspezione -
Indica a Codegen le tue operazioni: Usa pattern glob come
src/**/*.tscosì Codegen trova query, mutation e fragment nel tuo codice -
Configura plugin e target di output: Indica a Codegen quali file scrivere e quali plugin generano ciascuno di essi
-
Esegui Codegen: Esegui
npx graphql-codegenper caricare lo schema, validare le operazioni e scrivere l'output -
Usa gli artifact generati: Importa i tipi e i document node generati nel codice dell'applicazione
I passaggi da 1 a 3 si mettono in un unico file di configurazione, di solito codegen.ts. Ecco la versione minima per un progetto di catalogo corsi:
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
// Step 1: Where the schema comes from
schema: "schema.graphql",
// Step 2: Where your operations are placed
documents: ["src/**/*.ts"],
// Step 3: What to generate and where to write it
generates: {
"./src/gql/": { preset: "client" },
},
};
export default config;
Il preset client è un bundle di plugin che genera in un colpo solo i tipi di schema, i tipi delle operazioni e i typed document node. Tratterò ciascuno di questi nelle sezioni su setup e configurazione.
Come impostare GraphQL Codegen
Il modo migliore per capire Codegen è eseguirlo su un progetto reale.
In questa sezione creerai da zero un piccolo progetto di catalogo corsi. Ha uno schema con corsi e docenti, una query e una configurazione Codegen che trasforma la query in tipi TypeScript. Ti bastano Node.js e npm.
Passaggio 1: Installa GraphQL Code Generator
Crea una nuova cartella e inizializza un progetto npm:
mkdir course-catalog && cd course-catalog
npm init -y
Codegen richiede un paio di pacchetti. Due vanno tra le dipendenze normali, e gli altri come dipendenze di sviluppo perché usati solo in fase di dev:
npm install graphql @graphql-typed-document-node/core
npm install -D typescript @graphql-codegen/cli @graphql-codegen/typescript-operations @graphql-codegen/typed-document-node
Ecco cosa fa ciascun pacchetto:
-
graphql: L'implementazione di riferimento di GraphQL per JavaScript, che Codegen usa per analizzare schema e operazioni -
@graphql-typed-document-node/core: Fornisce il tipoTypedDocumentNodeimportato dal codice generato -
@graphql-codegen/cli: Lo strumento da riga di comando di Codegen -
@graphql-codegen/typescript-operations: Il plugin che genera i tipi per query, mutation e fragment -
@graphql-codegen/typed-document-node: Il plugin che genera oggetti documento con i tipi allegati
Passaggio 2: Crea un file di configurazione
Codegen legge le sue impostazioni da un file di config nella root del progetto. Crea codegen.ts con una config vuota per ora:
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {};
export default config;
Puoi anche eseguire npx graphql-codegen init per rispondere a un paio di domande e ottenere un file di config generato. Qui lo scrivo a mano così puoi vedere cosa fa ogni impostazione.
Passaggio 3: Specifica le posizioni di schema e documenti
Codegen ha bisogno di due input, quindi creiamoli entrambi.
Per prima cosa, salva lo schema come schema.graphql nella root del progetto:
enum Level {
BEGINNER
INTERMEDIATE
ADVANCED
}
type Instructor {
id: ID!
name: String!
bio: String
}
type Course {
id: ID!
title: String!
level: Level!
durationHours: Int!
instructor: Instructor!
}
type Enrollment {
id: ID!
course: Course!
studentEmail: String!
}
type Query {
courses(level: Level, limit: Int): [Course!]!
course(id: ID!): Course
}
type Mutation {
enrollStudent(courseId: ID!, studentEmail: String!): Enrollment!
}
Poi crea src/operations/courses.graphql con una query che recupera corsi e relativi docenti:
query GetCourses($level: Level, $limit: Int) {
courses(level: $level, limit: $limit) {
id
title
level
instructor {
name
}
}
}
Ora punta Codegen a entrambi i file. La chiave schema prende il percorso dello schema e documents un pattern glob che corrisponde alle tue operazioni:
const config: CodegenConfig = {
schema: "schema.graphql",
documents: ["src/**/*.graphql"],
};
Passaggio 4: Scegli un file di output
La chiave generates dice a Codegen dove scrivere l'output. Ogni chiave al suo interno è un percorso di file:
const config: CodegenConfig = {
schema: "schema.graphql",
documents: ["src/**/*.graphql"],
generates: {
"src/generated/graphql.ts": {},
},
};
Codegen crea la cartella src/generated/ se non esiste.
Passaggio 5: Aggiungi i plugin rilevanti
Un file di output senza plugin è vuoto. Aggiungi i due plugin che hai installato:
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
schema: "schema.graphql",
documents: ["src/**/*.graphql"],
generates: {
"src/generated/graphql.ts": {
plugins: ["typescript-operations", "typed-document-node"],
},
},
};
export default config;
Se hai seguito un tutorial più vecchio, probabilmente hai visto anche il plugin typescript in questo elenco. Ora puoi rimuoverlo, perché typescript-operations funziona da solo e genera solo i tipi Input, Enum e Operation effettivamente usati. Se metti entrambi nello stesso file di output con le versioni attuali, TypeScript genera errori di dichiarazioni duplicate.
Passaggio 6: Esegui Codegen
Aggiungi uno script npm così non devi ricordarti il comando:
npm pkg set scripts.codegen="graphql-codegen"
Poi eseguilo:
npm run codegen
Codegen carica lo schema, valida la query rispetto ad esso e scrive l'output:

Risultati della generazione con Codegen
Se un'operazione fa riferimento a un campo che non esiste nello schema, l'esecuzione fallisce qui e non viene scritto alcun file.
Passaggio 7: Ispeziona l'output generato
Apri src/generated/graphql.ts. Ecco cosa vedrai:

L'output generato
Codegen ha generato quattro cose:
-
Level: Una union di stringhe per l'enum, generata solo perché la query lo usa -
GetCoursesQueryVariables: Il tipo per le variabili che passi alla query, dove entrambe sono opzionali perché lo schema non le segna come richieste -
GetCoursesQuery: La forma della risposta, con solo i campi richiesti dalla query -
GetCoursesDocument: La query parsata come typed document node, con i tipi di risultato e variabili allegati
Guarda di nuovo GetCoursesQuery. Non include durationHours né la bio del docente, perché la query non li richiede. Se provi a leggere course.durationHours nel tuo codice, otterrai un errore in compilazione, non un undefined a runtime.
E questo è tutto il setup: tu scrivi GraphQL e Codegen scrive TypeScript.
Come configurare GraphQL Codegen
La maggior parte delle configurazioni di Codegen che vedrai è più lunga di quella sopra, ma è costruita sulle stesse quattro impostazioni.
Il file di config può essere codegen.ts, codegen.yml o codegen.json. Uso TypeScript perché ottieni autocomplete e type check anche sulla config. Le impostazioni sono le stesse in ogni formato.
Schema
La chiave schema indica a Codegen dove si trova il tuo schema. Accetta diversi tipi di sorgenti.
Un file locale o un pattern glob vanno bene quando lo schema è nel tuo repo:
schema: "schema.graphql",
// or, for a schema split across files
schema: "schema/**/*.graphql",
Un URL funziona quando lo schema è su un server in esecuzione. Codegen invia una query di introspezione all'endpoint e puoi passare header per l'autenticazione:
schema: [
{
"https://api.example.com/graphql": {
headers: { Authorization: Bearer ${process.env.API_TOKEN} },
},
},
],
L'opzione URL funziona solo se il server ha l'introspezione abilitata. Molte API in produzione la disabilitano, quindi per la CI un file di schema committato nel repo è di solito la scelta più sicura.
Documents
La chiave documents dice a Codegen dove trovare le tue query, mutation, subscription e fragment.
Le operazioni possono stare in file .graphql o inline nel tuo codice TypeScript. Codegen trova le operazioni inline quando sono racchiuse in un tag gql o contrassegnate con un commento /* GraphQL */:
const EnrollStudent = /* GraphQL */
mutation EnrollStudent($courseId: ID!, $studentEmail: String!) {
enrollStudent(courseId: $courseId, studentEmail: $studentEmail) {
id
course {
title
}
}
}
;
Per scansionare entrambi i tipi di file, amplia il glob. Aggiungi un pattern negato così Codegen non scansiona il proprio output:
documents: ["src/**/*.{graphql,ts}", "!src/generated/**"],
Dai a ogni operazione un nome univoco. Codegen costruisce i nomi dei tipi dai nomi delle operazioni, quindi EnrollStudent diventa EnrollStudentMutation e EnrollStudentMutationVariables.
Generates
La chiave generates mappa i percorsi di output a ciò che va al loro interno. Puoi avere tutti i target di output che ti servono, ciascuno con i propri plugin.
Poniamo che tu voglia i tipi delle operazioni in un file e i tipi completi di schema in un altro:
generates: {
"src/generated/graphql.ts": {
plugins: ["typescript-operations", "typed-document-node"],
config: {
useTypeImports: true,
},
},
"src/generated/schema-types.ts": {
plugins: ["typescript"],
},
},
L'oggetto config dentro un target di output si applica solo a quel file. Qui, useTypeImports fa sì che il file generato usi import type, che alcuni setup di build richiedono. Se metti config alla root del file, si applica a ogni output.
Un percorso di output può anche essere una cartella che termina con uno slash, come "src/gql/". È il formato che usano i preset, dato che scrivono più di un file.
Plugin
I plugin decidono cosa viene generato. Codegen carica schema e documenti, e ogni plugin li trasforma in una parte dell'output.
Questi sono i plugin che vedrai più spesso nei progetti TypeScript:
-
typescript-operations: Tipi per query, mutation, subscription e fragment, più gli enum e gli input che usano -
typed-document-node: Oggetti documento con i tipi di risultato e variabili allegati, che la maggior parte dei client GraphQL comprende -
typescript: Tipi per ogni oggetto, enum e input nello schema, che le tue operazioni li usino o no -
typescript-resolvers: Firme di tipo per le funzioni resolver lato server
Un preset è un bundle di plugin con una config predefinita sensata. Il preset client della sezione Come funziona è quello che The Guild consiglia per le app client. Si basa sotto il cofano sul typescript-operations migliorato, così ottieni i suoi vantaggi senza setup extra.
Ogni plugin ha le sue opzioni di configurazione, e sono molte. Non devi impararle tutte in anticipo. Parti dai default e cerca un'opzione quando l'output generato non corrisponde a ciò che il tuo codice si aspetta.
Cosa può generare GraphQL Codegen?
Quello che ottieni da Codegen dipende dai plugin che scegli.
Lo stesso schema e le stesse operazioni possono produrre semplici tipi TypeScript o codice client pronto all'uso. Ecco gli output che userai più spesso.
Tipi TypeScript
Il plugin typescript genera un tipo per ogni oggetto, enum e input del tuo schema. Per il catalogo corsi, il tipo Course appare così:
export type Course = {
__typename?: 'Course';
durationHours: Scalars['Int']['output'];
id: Scalars['ID']['output'];
instructor: Instructor;
level: Level;
title: Scalars['String']['output'];
};
Questi tipi descrivono l'intero schema, il che li rende utili lato server, nei fixture di test e nelle funzioni di utilità che lavorano con oggetti completi.
Ma sono poco adatti al codice client. Un tipo Course dice che ogni campo esiste, anche quelli che la tua query non ha mai richiesto.
Tipi per le operazioni
Il plugin typescript-operations genera due tipi per operazione: uno per il risultato e uno per le variabili. Funziona allo stesso modo per query e subscription.
Ecco cosa genera per una mutation EnrollStudent:
export type EnrollStudentMutationVariables = Exact<{
courseId: string | number;
studentEmail: string;
}>;
export type EnrollStudentMutation = { enrollStudent: { id: string, course: { title: string } } };
Il tipo di risultato corrisponde al selection set, campo per campo. Il tipo di variabili è Exact, quindi TypeScript rifiuta variabili extra o con errori di ortografia.
La variabile courseId accetta sia string sia number perché lo scalare ID di GraphQL accetta entrambi come input. Dalla v6, il tipo predefinito è string | number, che è il tipo corretto per i casi d'uso client.
Document node tipizzati
Un normale DocumentNode è una query GraphQL parsata. Conosce la struttura della query, ma non sa cosa restituisce né quali variabili accetta.
Un typed document node ha entrambi i tipi associati. Il plugin typed-document-node ne genera uno per operazione:
export const GetCoursesDocument = { /* ... */ } as unknown as DocumentNode<GetCoursesQuery, GetCoursesQueryVariables>;
In questo modo, qualsiasi funzione che accetta un TypedDocumentNode può inferire i tipi di risultato e variabili dal solo documento. Non passi generic a mano e non puoi combinare una query con i tipi sbagliati.
La maggior parte dei client GraphQL moderni accetta typed document node, inclusi Apollo Client, urql e graphql-request. Più avanti in questo articolo li vedrai tutti e tre in azione.
Integrazioni specifiche per client
Alcuni plugin vanno oltre e generano codice per un client o framework specifico. Ad esempio:
-
typescript-react-apollo: Genera hook React tipizzati per Apollo Client -
typescript-urql: Genera hook e componenti tipizzati per urql -
typescript-react-query: Genera hook tipizzati per TanStack Query -
typescript-graphql-request: Genera un SDK tipizzato con una funzione per operazione
Tutti questi sono plugin della community. The Guild li ha spostati in un repository community separato, quindi il loro ritmo di rilascio è diverso dai plugin core.
Inoltre ne hai meno bisogno di un tempo. Quando un client accetta typed document node, gli hook generati non aggiungono molta sicurezza di tipo. La documentazione di Apollo, per esempio, consiglia di usare il plugin typescript-operations, che si concentra sulla sola generazione dei tipi e non include codice runtime aggiuntivo.
In generale, dovresti iniziare con tipi delle operazioni e typed document node. Poi, aggiungi un plugin specifico del client solo se il tuo team vuole gli hook o le funzioni SDK generati.
GraphQL Codegen con TypeScript
I tipi generati contano solo se intercettano gli errori, quindi facciamone qualcuno.
Questo esempio estende il progetto del catalogo corsi con una seconda query, un piccolo helper che la invia all'API e il codice che usa il risultato. Presuppone un server GraphQL che implementa lo schema in esecuzione su http://localhost:4000/graphql.
Ecco la parte di schema usata in questo esempio:
type Instructor {
id: ID!
name: String!
bio: String
}
type Course {
id: ID!
title: String!
level: Level!
durationHours: Int!
instructor: Instructor!
}
type Query {
course(id: ID!): Course
}
Qui contano due dettagli. Il campo course restituisce Course senza !, quindi è null quando nessun corso corrisponde all'ID. Anche la bio del docente è nullable.
Crea src/operations/course.graphql con una query che recupera un corso:
query GetCourse($id: ID!) {
course(id: $id) {
title
durationHours
instructor {
name
bio
}
}
}
Esegui di nuovo npm run codegen. Codegen aggiunge due nuovi tipi a src/generated/graphql.ts:

Contenuto del file graphql.ts
La nullabilità dallo schema si trasferisce. course può essere null, bio può essere null e id è obbligatorio perché la query lo dichiara come ID!.
Ora il codice dell'applicazione. Crea src/execute.ts con un helper che invia qualsiasi documento tipizzato all'API:
import { print } from "graphql";
import type { TypedDocumentNode } from "@graphql-typed-document-node/core";
const API_URL = "http://localhost:4000/graphql";
export async function execute<TResult, TVariables>(
document: TypedDocumentNode<TResult, TVariables>,
variables: TVariables,
): Promise<TResult> {
const response = await fetch(API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: print(document), variables }),
});
const { data, errors } = await response.json();
if (errors?.length) {
throw new Error(errors[0].message);
}
return data as TResult;
}
L'helper non sa nulla sui corsi. Ottiene entrambi i tipi dal documento che passi e la funzione print() trasforma il documento di nuovo in una stringa di query per la richiesta.
Poi usalo in src/course-details.ts:
import { execute } from "./execute";
import { GetCourseDocument, type GetCourseQuery } from "./generated/graphql";
function formatCourse(course: NonNullable<GetCourseQuery["course"]>): string {
const bio = course.instructor.bio ?? "No bio yet";
return course.title({course.durationHours}h) by ${course.instructor.name} - ${bio};
}
const { course } = await execute(GetCourseDocument, { id: "c1" });
if (course) {
console.log(formatCourse(course));
} else {
console.log("Course not found");
}
La funzione formatCourse() prende il tipo del suo parametro dal tipo di query generato. NonNullable rimuove il caso null, perché la funzione viene eseguita solo dopo il controllo nell'if.
L'esempio usa l'await al livello superiore, quindi imposta "type": "module" in package.json ed eseguilo con npx tsx src/course-details.ts:

Chiamata riuscita a un server GraphQL mock
Ora, questo presuppone che tu abbia un server GraphQL in esecuzione. Ecco un mock che ho usato:
import { createServer } from "node:http";
import { readFileSync } from "node:fs";
import { createSchema, createYoga } from "graphql-yoga";
const instructors = [{ id: "i1", name: "Dario Radecic", bio: "Developer" }];
const courses = [
{ id: "c1", title: "Intro to GraphQL", level: "BEGINNER", durationHours: 4, instructor: instructors[0] },
{ id: "c2", title: "GraphQL Schema Design", level: "ADVANCED", durationHours: 6, instructor: instructors[0] },
];
const schema = createSchema({
typeDefs: readFileSync("schema.graphql", "utf8"),
resolvers: {
Query: {
courses: (_: unknown, args: { level?: string; limit?: number }) =>
courses
.filter((course) => !args.level || course.level === args.level)
.slice(0, args.limit ?? undefined),
course: (_: unknown, args: { id: string }) =>
courses.find((course) => course.id === args.id) ?? null,
},
Mutation: {
enrollStudent: (_: unknown, args: { courseId: string; studentEmail: string }) => ({
id: "e1",
course: courses.find((course) => course.id === args.courseId),
studentEmail: args.studentEmail,
}),
},
},
});
createServer(createYoga({ schema })).listen(4000, () => {
console.log("Mock API running at http://localhost:4000/graphql");
});
Ora prova a romperlo. Apri src/course-details.ts e sostituisci tutto sotto la funzione formatCourse() con tre errori che i tipi scritti a mano spesso lasciano passare:
// Missing the required id variable
await execute(GetCourseDocument, {});
const { course } = await execute(GetCourseDocument, { id: "c1" });
// Reading a field the query doesn't request
console.log(course?.level);
// Forgetting that course can be null
console.log(course.title);
Poi esegui il type check del progetto:
npx tsc -p .
TypeScript intercetta tutti e tre prima che il codice venga eseguito:

Errori intercettati da TypeScript
Ecco come Codegen riduce le discrepanze tra frontend e API. I tipi provengono dallo stesso schema usato dal server, quindi non possono descrivere campi inesistenti o mancare null che il server può restituire.
E la protezione non si ferma dopo la prima esecuzione. Mettiamo che il team backend renda durationHours nullable. Rigeneri, il tipo diventa number | null e TypeScript evidenzia ogni riga che presumeva un numero.
GraphQL Codegen con i client GraphQL
Codegen non invia richieste. È il compito del tuo client GraphQL.
I typed document node generati sono il punto di passaggio tra i due. Li passi al client, e il client inferisce i tipi. Gli esempi qui sotto usano lo stesso file src/generated/graphql.ts e la mock API della sezione precedente, quindi tieni npx tsx mock/server.ts in esecuzione in un terminale separato.
GraphQL Codegen con Apollo
Installa Apollo Client e la sua dipendenza peer rxjs:
npm install @apollo/client rxjs
L'esempio esegue anche la mutation EnrollStudent. Salvala come src/operations/enroll.graphql:
mutation EnrollStudent($courseId: ID!, $studentEmail: String!) {
enrollStudent(courseId: $courseId, studentEmail: $studentEmail) {
id
course {
title
}
}
}
La documentazione di Apollo consiglia anche due opzioni di config per Codegen, dato che Apollo Client include sempre i campi __typename ma non li aggiunge ai tipi root. Aggiungile al target di output in codegen.ts:
"src/generated/graphql.ts": {
plugins: ["typescript-operations", "typed-document-node"],
config: {
nonOptionalTypename: true,
skipTypeNameForRoot: true,
},
},
Con queste opzioni, ogni oggetto nei tipi di risultato generati ottiene un campo __typename obbligatorio, come __typename: 'Course'. Questo corrisponde a quanto restituisce la cache di Apollo.
Esegui di nuovo npm run codegen per rilevare la nuova mutation e la config.
Apollo Client accetta typed document node in query() e mutate(). Salva questo come src/apollo.ts:
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
import { EnrollStudentDocument, GetCoursesDocument } from "./generated/graphql";
const client = new ApolloClient({
link: new HttpLink({ uri: "http://localhost:4000/graphql" }),
cache: new InMemoryCache(),
});
const { data } = await client.query({
query: GetCoursesDocument,
variables: { level: "BEGINNER", limit: 5 },
});
data?.courses.forEach((course) => console.log(course.title));
const result = await client.mutate({
mutation: EnrollStudentDocument,
variables: { courseId: "c1", studentEmail: "student@example.com" },
});
console.log(result.data?.enrollStudent.course.title);
L'oggetto data è tipizzato come GetCoursesQuery | undefined, e TypeScript verifica variables rispetto a GetCoursesQueryVariables. La mutation funziona allo stesso modo con EnrollStudentMutation e le sue variabili.
Eseguila con npx tsx src/apollo.ts:

Output di Apollo
La prima riga proviene dalla query, dato che è l'unico corso per principianti. La seconda viene dalla mutation, che restituisce il titolo del corso a cui lo studente si è iscritto.
Gli hook React, come useQuery() e useMutation(), inferiscono i tipi dai typed document node allo stesso modo.
GraphQL Codegen con urql
Installa il pacchetto core di urql:
npm install @urql/core
urql funziona con i typed document node out of the box. Passa il documento come primo argomento a query() in src/urql.ts:
import { Client, cacheExchange, fetchExchange } from "@urql/core";
import { GetCoursesDocument } from "./generated/graphql";
const client = new Client({
url: "http://localhost:4000/graphql",
exchanges: [cacheExchange, fetchExchange],
});
const result = await client
.query(GetCoursesDocument, { level: "ADVANCED" })
.toPromise();
if (result.error) {
throw result.error;
}
result.data?.courses.forEach((course) => console.log(course.title));
Il secondo argomento è tipizzato come le variabili della query e result.data è tipizzato come il risultato della query.
Eseguilo con npx tsx src/urql.ts:

Output di Urql
I binding per i framework, come useQuery() nel pacchetto urql per React, accettano anch'essi typed document node.
GraphQL Codegen con altri client
Non ti serve un client completo per ottenere risultati tipizzati.
Il client graphql-request è un client minimale che invia una richiesta e restituisce i dati. Installalo con il seguente comando:
npm install graphql-request
Inferisce i tipi dai typed document node proprio come Apollo e urql. Salva questo come src/graphql-request.ts:
import { request } from "graphql-request";
import { GetCourseDocument } from "./generated/graphql";
const { course } = await request(
"http://localhost:4000/graphql",
GetCourseDocument,
{ id: "c2" },
);
console.log(course?.title);
Eseguilo con npx tsx src/graphql-request.ts:

Output di GraphQL request
La stessa idea funziona con TanStack Query. Non invia richieste GraphQL da solo, quindi chiami graphql-request o l'helper execute() della sezione precedente all'interno della funzione di query, e il tipo del risultato fluisce nel tuo codice.
E se il tuo client non supporta affatto i typed document node, puoi comunque usare i semplici tipi delle operazioni. Importa GetCourseQuery e GetCourseQueryVariables e passali come generics. È più lavoro manuale, ma i tipi provengono comunque dallo schema.
Come eseguire automaticamente GraphQL Codegen
I tipi generati sono utili solo se corrispondono allo schema e alle operazioni attuali.
Se ti dimentichi di rieseguire Codegen dopo una modifica, i tipi descrivono un codice che non esiste più. È lo stesso problema di drift dei tipi scritti a mano, solo con passaggi extra. La soluzione è togliere il passaggio manuale dal flusso di lavoro.
Script npm
Inizia con un paio di script in package.json, così tutti nel team eseguono Codegen allo stesso modo:
{
"scripts": {
"codegen": "graphql-codegen",
"codegen:watch": "graphql-codegen --watch",
"codegen:check": "graphql-codegen --check",
"prebuild": "npm run codegen",
"build": "tsc -p ."
}
}
Il resto di questa sezione spiega a cosa serve ciascuno.
Watch mode
La modalità watch riesegue Codegen ogni volta che cambia un file monitorato. Ha bisogno del pacchetto @parcel/watcher, che Codegen non installa per te:
npm install -D @parcel/watcher
npm run codegen:watch
Se salti l'installazione, Codegen gira una volta e stampa Failed to import @parcel/watcher invece di mettersi in ascolto.
La watch mode traccia sia le tue operazioni sia un file di schema locale. Se aggiungi un campo a course.graphql, il nuovo campo compare in GetCourseQuery un secondo dopo. Se rendi durationHours nullable in schema.graphql, il tipo diventa number | null e il tuo editor segnala ogni riga che presumeva un numero.
Tienila in esecuzione in un terminale accanto al tuo dev server durante lo sviluppo.
Genera prima delle build
npm esegue uno script chiamato prebuild prima di build. Con gli script sopra, npm run build rigenera prima i tipi e solo dopo esegue il compilatore TypeScript.
In questo modo, una build non gira mai con tipi obsoleti, anche se qualcuno si è dimenticato di eseguire Codegen dopo aver fatto pull di modifiche.
CI/CD
Il flag --check esegue Codegen in modalità dry-run. Non scrive file, confronta ciò che genererebbe con ciò che c'è su disco ed esce con codice 1 se qualcosa differisce.
Ecco un workflow minimale di GitHub Actions che lo usa:
name: CI
on: [push, pull_request]
jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
- run: npm ci
- run: npm run codegen:check
- run: npm run build
Lo step codegen:check ha senso solo se fai commit dei file generati nel repo. Se non lo fai, rimuovi quello step, dato che npm run build rigenera tutto tramite l'hook prebuild comunque.
Rigenera quando lo schema cambia
Le operazioni cambiano nel tuo repo, quindi la watch mode e prebuild le coprono. I cambi di schema sono più difficili, perché di solito arrivano da un altro team.
Come gestirli dipende da dove si trova lo schema:
- File di schema nello stesso repo: La watch mode e la CI rilevano le modifiche: è il setup più semplice con cui lavorare
- Schema da URL: Codegen recupera la versione più recente a ogni esecuzione, quindi
codegen:checkin CI fallisce non appena lo schema del server non corrisponde più al tuo output committato - Schema pubblicato da un altro repo: Copialo o scaricalo come parte della build, poi esegui Codegen sulla copia locale
L'opzione URL ha un'unica incognita. La watch mode reagisce ai cambi di file, quindi non si accorge quando cambia uno schema remoto. Vedrai quei cambi solo alla prossima esecuzione manuale o job di CI.
GraphQL Codegen vs tipi scritti a mano
Entrambi gli approcci ti danno tipi TypeScript per i dati GraphQL, ma ci sono alcune differenze di cui dovresti essere consapevole.
Tipi scritti a mano
Con i tipi scritti a mano leggi lo schema e scrivi tu stesso le interfacce corrispondenti.
Questo ti dà pieno controllo. Puoi nominare i tipi come vuoi, unire un paio di operazioni in un tipo condiviso, o aggiungere tipi di supporto che lo schema non ha.
Ma è lavoro manuale. Ogni nuovo campo o argomento rinominato significa trovare e aggiornare a mano il tipo corrispondente. E niente ti avvisa quando ne salti uno.
Da qui nasce il drift. L'interfaccia Course dell'introduzione continuava a compilare dopo che il backend aveva rinominato title, perché TypeScript controlla solo il tuo codice rispetto ai tuoi tipi, non rispetto all'API.
Tipi generati
I tipi generati provengono dallo stesso schema che usa il server, quindi sono sincronizzati con le tue definizioni GraphQL per progettazione. Un cambio di schema più un'esecuzione di Codegen aggiornano tutti i tipi interessati in una volta sola.
Scrivi anche molto meno codice. Il progetto del catalogo corsi ha tre operazioni e zero tipi scritti a mano per ciascuna di esse.
E ottieni uno sviluppo guidato dallo schema quasi gratis. Modifichi lo schema o un'operazione, rigeneri, e il compilatore ti dà l'elenco di tutto ciò che deve cambiare.
Ma ricordati che Codegen riflette solo ciò che gli dai.
Se lo schema segna ogni campo come nullable, i tipi generati sono pieni di controlli | null. Se un'operazione recupera 40 campi quando alla pagina ne servono cinque, il tipo generato ha 40 campi.
Ecco un rapido riepilogo delle differenze:
| Tipi scritti a mano | Tipi generati | |
|---|---|---|
| Manutenzione | Manuale per ogni modifica allo schema | Un comando dopo ogni modifica |
| Schema drift | Possibile e silenzioso | Intercettato in generazione o in compilazione |
| Boilerplate | Aumenta con ogni operazione | Nessuno da scrivere a mano |
| Controllo sui tipi personalizzati | Totale | Limitato alle opzioni dei plugin |
| Sviluppo guidato dallo schema | Verifiche incrociate manuali | Il compilatore mostra ogni riga interessata |
Confronto tra Codegen e tipi scritti a mano
GraphQL Codegen vs. introspezione GraphQL
I due vengono confusi perché Codegen spesso usa l'introspezione. Ma risolvono problemi diversi.
L'introspezione è una funzionalità della specifica GraphQL. Qualsiasi server GraphQL può rispondere a query sul proprio schema tramite campi speciali come __schema e __type. Il risultato è una descrizione JSON di ogni tipo, campo e argomento esposto dall'API.
In parole semplici, l'introspezione risponde alla domanda "Com'è fatta questa API?"
Codegen risponde alla domanda "Quale codice serve alla mia applicazione?". Prende le informazioni dello schema e le tue operazioni, e scrive tipi TypeScript e typed document node.
Quando imposti schema a un URL in codegen.ts, Codegen invia una query di introspezione a quel server per ottenere lo schema. Con un file .graphql locale, l'introspezione non avviene affatto.
Codegen può anche produrre un risultato di introspezione come output. Il plugin @graphql-codegen/introspection scrive lo schema come JSON di introspezione, che alcuni strumenti, come Graphcache di urql, usano per il caching consapevole dello schema.
| Introspezione | GraphQL Codegen | |
|---|---|---|
| Cos'è | Sistema di query integrato in GraphQL | Strumento da riga di comando e plugin |
| Input | Una query a un server in esecuzione | Uno schema e le tue operazioni |
| Output | Descrizione JSON dello schema | Tipi, document node, codice client |
| Quando gira | Ogni volta che un client interroga il server | Durante sviluppo, build e CI |
Confronto tra Codegen e introspezione
Problemi comuni di GraphQL Codegen
La maggior parte degli errori di Codegen ricade in uno di quattro problemi.
Mancano tipi generati
Codegen gira senza errori, ma il tipo che ti aspetti non è nel file di output.
Controlla queste tre cose in ordine:
-
Schema: Se il tipo non esiste nello schema che Codegen carica, non può essere generato, quindi apri il file di schema o esegui tu stesso la query di introspezione e conferma che ci sia
-
Documents: I tipi delle operazioni esistono solo per operazioni che Codegen ha trovato, quindi se il file con la tua query non è coperto dal glob
documents, i suoi tipi mancano -
Plugin: Dalla v6,
typescript-operationsgenera solo enum e input usati dalle tue operazioni, quindi se ti serve un tipo di schema completo comeCourse, aggiungi il plugintypescriptin un file di output separato
Se usi un plugin che non hai installato, Codegen fallisce con un messaggio chiaro:
Unable to find template plugin matching 'typescript-react-apollo'
Install one of the following packages:
- @graphql-codegen/typescript-react-apollo
Codegen non trova le operazioni
Questo errore significa che il glob documents non ha trovato alcun file con un'operazione al suo interno:
Unable to find any GraphQL type definitions for the following pointers:
- src/**/*.gql
Controlla che l'estensione nel glob corrisponda ai tuoi file. Un pattern .gql non troverà i file .graphql, e un pattern src/**/*.graphql non troverà operazioni inline nei file .ts.
Per le operazioni inline, controlla anche il tag. Codegen trova solo i template literal racchiusi in gql o contrassegnati con un commento /* GraphQL */. Una semplice template string con una query all'interno per lui è invisibile.
Se imposti ignoreNoDocuments: true, Codegen non mostra questo errore e genera un output senza tipi di operazione. Questa opzione è utile in un progetto nuovo di zecca, ma può nascondere un glob rotto in seguito.
I tipi generati sono obsoleti
I tipi compilano, ma non riflettono l'ultima modifica a un'operazione o allo schema.
Codegen non gira da solo. Rieseguilo dopo ogni modifica a un file .graphql, a un'operazione inline, allo schema o a codegen.ts. Durante lo sviluppo, npm run codegen:watch lo fa per te.
Se i tipi sono obsoleti in CI, fai commit dei file rigenerati o lascia che l'hook prebuild li rigeneri prima della build.
I tipi generati non corrispondono all'API
I tipi compilano, ma i dati dal server hanno una forma diversa a runtime.
Quasi sempre significa che Codegen e la tua app parlano con schemi diversi. Per esempio, Codegen legge un schema.graphql locale vecchio di una settimana, mentre l'app chiama un'API di produzione cambiata nel frattempo.
Controlla dove punta la chiave schema. Se è un file, confrontalo con l'API che la tua app chiama e aggiornalo. Se è un URL, assicurati che sia lo stesso ambiente usato dalla tua app, non un server di staging con schema più nuovo o più vecchio.
Poi rigenera e guarda il diff. Se un file di operazione è cambiato ma quello generato no, probabilmente l'operazione è fuori dal glob documents.
Best practice per GraphQL Codegen
Codegen non richiede molta configurazione per funzionare bene. Queste pratiche lo mantengono tale mentre il progetto cresce:
-
Tieni sincronizzati schema e file generati: Esegui la watch mode durante lo sviluppo e
prebuildprima delle build, così nessuno lavora con tipi da uno schema vecchio -
Genera tipi specifici per le operazioni: Usa
typescript-operationsper il codice client perché corrispondono esattamente a ciò che ogni query recupera -
Fai commit dei file generati solo se il tuo workflow ne ha bisogno: I file committati ti permettono di rivedere le modifiche ai tipi nelle pull request e di usare
--checkin CI; se al tuo team non serve, aggiungi la cartella di output a.gitignoree genera a ogni build -
Mantieni la config breve: Un file di output con due plugin copre la maggior parte dei progetti client, e ogni opzione extra è qualcosa che il prossimo sviluppatore deve capire prima di cambiare qualsiasi cosa
-
Esegui Codegen in CI a ogni push: Uno step
--checko un hookprebuildintercetta le modifiche che qualcuno ha dimenticato di rigenerare in locale -
Non modificare mai a mano i file generati: La prossima esecuzione di Codegen sovrascrive le tue modifiche, quindi correggi invece lo schema, l'operazione o la config
Conclusione
GraphQL Codegen si occupa della parte più ripetitiva dello sviluppo GraphQL: trasformare schemi e operazioni in tipi TypeScript a mano.
Il workflow resta lo stesso a prescindere da quanto cresce il progetto. Definisci schema e operazioni, configuri Codegen, generi i tipi e i typed document node e li importi nel codice della tua applicazione.
Il valore più grande è che i tuoi tipi descrivono sempre l'API che chiami davvero, non il codice che non devi scrivere. Quando lo schema cambia, il compilatore ti dirà se qualcosa si è rotto così puoi intercettare gli errori in anticipo.
Se vuoi scoprire come GraphQL viene usato in un moderno database NoSQL, leggi il nostro post MongoDB e GraphQL.
FAQ su GraphQL Codegen
A cosa serve GraphQL Codegen?
GraphQL Codegen genera codice da uno schema GraphQL e dalle tue operazioni GraphQL. La maggior parte dei team lo usa per ottenere tipi TypeScript e typed document node per query e mutation, così da non dover scrivere e aggiornare quei tipi a mano. Può anche generare i tipi dei resolver per i server e hook pronti all'uso per client specifici.
Ho bisogno di un server GraphQL in esecuzione per usare Codegen?
No. Codegen può leggere lo schema da un file .graphql locale, quindi funziona senza alcun server. Ti serve un server in esecuzione solo se punti la chiave schema a un URL, perché in quel caso Codegen recupera lo schema via introspezione.
Devo fare commit dei file generati su Git?
Dipende dal workflow del tuo team. I file committati ti permettono di rivedere le modifiche ai tipi nelle pull request e di usare graphql-codegen --check in CI per intercettare output obsoleti. Se non ti serve, aggiungi la cartella di output a .gitignore e rigenera i file prima di ogni build.
Dovrei usare il preset client o i plugin individuali?
The Guild consiglia il preset client per la maggior parte delle app client, perché include in un'unica configurazione tipi delle operazioni, typed document node e fragment masking. Apollo consiglia invece il plugin typescript-operations, perché il preset aggiunge codice runtime e funzionalità che confliggono con Apollo Client. Se usi Apollo, vai con i plugin. Altrimenti, il preset è un'ottima scelta predefinita.
Perché ricevo errori di dichiarazioni duplicate dopo l'aggiornamento di Codegen?
Dalla v6, il plugin typescript-operations genera i propri tipi enum e input. Se la tua config elenca ancora sia typescript sia typescript-operations per lo stesso file di output, entrambi i plugin dichiarano tipi come Level, e TypeScript genera errori di dichiarazioni duplicate. Rimuovi il plugin typescript da quell'output o spostalo in un file separato se ti servono ancora i tipi completi di schema.
