Last chance! 50% off DataCamp Premium
Sale ends in
Handgeschreven TypeScript-types voor een GraphQL-API zijn verouderd zodra iemand het schema wijzigt.
Stel, een backenddeveloper hernoemt het veld title op het type Course naar name. De frontend compileert nog steeds, omdat de handgeschreven Course-interface niet weet dat er iets is veranderd, dus de bug verschijnt pas tijdens runtime. In echte applicaties zijn er vaak tientallen queries, mutaties en fragments, dus je ziet hoe dit snel kan escaleren.
GraphQL Code Generator, of kortweg GraphQL Codegen, leest je schema en GraphQL-operaties en zet ze om in types, queryhelpers, clientintegraties en andere boilerplate die je anders met de hand zou schrijven. Het wordt meestal gebruikt met TypeScript en populaire clients zoals Apollo en urql. Met één commando houd je je types in sync zodra het schema verandert.
In dit artikel laat ik je zien hoe je GraphQL Codegen opzet in een klein TypeScript-project, hoe je het configureert, de gegenereerde types gebruikt en het automatisch laat draaien als onderdeel van je workflow.
Hoe verhoudt GraphQL zich tot REST? Lees onze Graph QL vs REST: A Complete Guide om de verschillen en voordelen te leren kennen.
GraphQL Codegen is een commandlinetool die GraphQL-definities omzet in applicatiecode.
Het is een open-sourceproject van The Guild, en je installeert het als devdependency in je project. Het verbindt drie dingen:
Het schema vertelt Codegen wat mogelijk is en de operaties vertellen wat je applicatie opvraagt.
Codegen combineert beide, zodat elk gegenereerd type exact overeenkomt met de vorm van het antwoord voor één operatie. Het valideert ook elke operatie tegen het schema. Het hernoemde veld title uit de introductie zou de generatie stoppen met een fout zoals Cannot query field "title" on type "Course", en je vangt het voordat de code draait.
De output hangt af van je plugins en configuratie. Codegen zelf schrijft geen TypeScript. Plugins doen dat. Zo genereert de plugin typescript schematypes, en de plugin typescript-operations genereert types voor je queries en mutaties. Er zijn ook plugins voor andere talen, zoals Java en C#, maar dit artikel blijft bij TypeScript.
Onthoud wel dat Codegen je GraphQL-server niet vervangt.
Het voert geen queries uit en haalt geen data op. Het leest GraphQL-definities tijdens ontwikkeling en schrijft bestanden. Je server en je GraphQL-client doen de rest.
Elke Codegen-setup volgt dezelfde flow: schema + GraphQL-operaties -> Codegen-configuratie -> gegenereerde code.

Diagram van de GraphQL Codegen-werkstroom
Dit is het algemene idee:
Voorzie in een schema: Verwijs Codegen naar een lokaal .graphql-bestand of een draaiend GraphQL-endpoint, dat Codegen via introspectie leest
Wijs Codegen naar je operaties: Gebruik globpatronen zoals src/**/*.ts zodat Codegen queries, mutaties en fragments in je code vindt
Configureer plugins en outputdoelen: Vertel Codegen welke bestanden te schrijven en welke plugins elk bestand genereren
Draai Codegen: Voer npx graphql-codegen uit om het schema te laden, je operaties te valideren en de output te schrijven
Gebruik de gegenereerde artefacten: Importeer de gegenereerde types en document nodes in je applicatiecode
Stappen 1 t/m 3 zet je in één configbestand, meestal codegen.ts. Dit is de kleinste versie ervan voor een cursuscatalogusproject:
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;
De preset client is een bundel van plugins die in één keer schematypes, operatietypes en getypeerde document nodes genereert. Ik behandel elk hiervan in de secties over setup en configuratie.
De beste manier om Codegen te begrijpen is het op een echt project te draaien.
In deze sectie bouw je vanaf nul een klein cursuscatalogusproject. Het heeft een schema met cursussen en docenten, één query, en een Codegen-config die de query omzet in TypeScript-types. Je hebt alleen Node.js en npm nodig.
Maak een nieuwe map en initialiseer een npm-project:
mkdir course-catalog && cd course-catalog
npm init -y
Codegen heeft een paar packages nodig. Twee gaan in reguliere dependencies, en de rest zijn devdependencies omdat ze alleen tijdens ontwikkeling draaien:
npm install graphql @graphql-typed-document-node/core
npm install -D typescript @graphql-codegen/cli @graphql-codegen/typescript-operations @graphql-codegen/typed-document-node
Dit doet elk package:
graphql: De referentie-implementatie van GraphQL voor JavaScript, die Codegen gebruikt om het schema en de operaties te parsen
@graphql-typed-document-node/core: Levert het type TypedDocumentNode dat je gegenereerde code importeert
@graphql-codegen/cli: De Codegen-commandlinetool
@graphql-codegen/typescript-operations: De plugin die types voor je queries, mutaties en fragments genereert
@graphql-codegen/typed-document-node: De plugin die documentobjecten genereert met de bijbehorende types
Codegen leest zijn instellingen uit een configbestand in de projectroot. Maak codegen.ts en zet er voorlopig een lege config in:
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {};
export default config;
Je kunt ook npx graphql-codegen init draaien om een paar vragen te beantwoorden en een gegenereerd configbestand te krijgen. Ik schrijf het hier met de hand zodat je ziet wat elke instelling doet.
Codegen heeft twee inputs nodig, dus laten we die beide maken.
Sla eerst het schema op als schema.graphql in de projectroot:
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!
}
Maak daarna src/operations/courses.graphql met een query die cursussen en hun docenten ophaalt:
query GetCourses($level: Level, $limit: Int) {
courses(level: $level, limit: $limit) {
id
title
level
instructor {
name
}
}
}
Wijs Codegen nu naar beide bestanden. De sleutel schema krijgt het schemapad, en documents krijgt een globpatroon dat je operaties matcht:
const config: CodegenConfig = {
schema: "schema.graphql",
documents: ["src/**/*.graphql"],
};
De sleutel generates vertelt Codegen waar de output moet komen. Elke sleutel daarbinnen is een bestandspad:
const config: CodegenConfig = {
schema: "schema.graphql",
documents: ["src/**/*.graphql"],
generates: {
"src/generated/graphql.ts": {},
},
};
Codegen maakt de map src/generated/ als die nog niet bestaat.
Een outputbestand zonder plugins is leeg. Voeg de twee plugins toe die je hebt geïnstalleerd:
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;
Als je een oudere tutorial hebt gevolgd, heb je waarschijnlijk ook de plugin typescript in deze lijst gezien. Je kunt die nu weglaten, want typescript-operations werkt zelfstandig en genereert alleen Input-, Enum- en Operatietypes die daadwerkelijk gebruikt worden. Als je beide in hetzelfde outputbestand zet op de huidige versies, gooit TypeScript fouten over dubbele declaraties.
Voeg een npm-script toe zodat je het commando niet hoeft te onthouden:
npm pkg set scripts.codegen="graphql-codegen"
Draai het vervolgens:
npm run codegen
Codegen laadt het schema, valideert de query ertegen en schrijft de output:

Resultaten van Codegen-generatie
Als een operatie verwijst naar een veld dat niet bestaat in het schema, faalt de run hier en worden er geen bestanden geschreven.
Open src/generated/graphql.ts. Dit is wat je ziet:

De gegenereerde output
Codegen genereerde vier dingen:
Level: Een string-unie voor de enum, alleen gegenereerd omdat de query hem gebruikt
GetCoursesQueryVariables: Het type voor de variabelen die je aan de query doorgeeft, waarbij beide optioneel zijn omdat het schema ze niet als vereist markeert
GetCoursesQuery: De vorm van het antwoord, met alleen de velden waar de query om vraagt
GetCoursesDocument: De geparste query als een getypeerde document node, met de resultaat- en variabelentypes gekoppeld
Kijk nog eens naar GetCoursesQuery. Het bevat geen durationHours of de bio van de docent, omdat de query er niet om vraagt. Als je in je code course.durationHours probeert te lezen, krijg je een compileerfout, geen undefined tijdens runtime.
En dat is de hele setup – jij schrijft GraphQL en Codegen schrijft de TypeScript.
De meeste Codegen-configs die je ziet zijn langer dan die hierboven, maar ze zijn opgebouwd uit dezelfde vier instellingen.
Het configbestand kan codegen.ts, codegen.yml of codegen.json zijn. Ik gebruik TypeScript omdat je autocomplete en typechecks op de config zelf krijgt. De instellingen zijn in elk formaat hetzelfde.
De sleutel schema vertelt Codegen waar je schema staat. Het accepteert een paar brontypes.
Een lokaal bestand of een globpatroon werkt wanneer het schema in je repo staat:
schema: "schema.graphql",
// or, for a schema split across files
schema: "schema/**/*.graphql",
Een URL werkt wanneer het schema op een draaiende server staat. Codegen stuurt een introspectiequery naar het endpoint, en je kunt headers doorgeven voor authenticatie:
schema: [
{
"https://api.example.com/graphql": {
headers: { Authorization: Bearer ${process.env.API_TOKEN} },
},
},
],
De URLooptie werkt alleen als de server introspectie aan heeft staan. Veel productie-API’s zetten dat uit, dus voor CI is een schemabestand dat is gecommit in de repo meestal de veiligere keuze.
De sleutel documents vertelt Codegen waar je queries, mutaties, subscriptions en fragments te vinden zijn.
Operaties kunnen in .graphql-bestanden staan of inline in je TypeScript-code. Codegen vindt inline operaties wanneer ze zijn omwikkeld met een gql-tag of gemarkeerd met een /* GraphQL */-commentaar:
const EnrollStudent = /* GraphQL */
mutation EnrollStudent($courseId: ID!, $studentEmail: String!) {
enrollStudent(courseId: $courseId, studentEmail: $studentEmail) {
id
course {
title
}
}
}
;
Om beide bestandstypen te scannen, verbreed je het globpatroon. Voeg een negatief patroon toe zodat Codegen zijn eigen output niet scant:
documents: ["src/**/*.{graphql,ts}", "!src/generated/**"],
Geef elke operatie een unieke naam. Codegen bouwt typenames op basis van operatienamen, dus EnrollStudent wordt EnrollStudentMutation en EnrollStudentMutationVariables.
De sleutel generates koppelt outputpaden aan wat erin komt. Je kunt zoveel outputdoelen hebben als nodig, elk met eigen plugins.
Stel dat je operatietypes in één bestand wilt en de volledige schematypes in een ander:
generates: {
"src/generated/graphql.ts": {
plugins: ["typescript-operations", "typed-document-node"],
config: {
useTypeImports: true,
},
},
"src/generated/schema-types.ts": {
plugins: ["typescript"],
},
},
Het config-object binnen een outputdoel geldt alleen voor dat bestand. Hier zorgt useTypeImports ervoor dat het gegenereerde bestand import type gebruikt, wat sommige buildsetups vereisen. Als je config in de root van het bestand plaatst, geldt het voor elke output.
Een outputpad kan ook een map zijn die eindigt op een slash, zoals "src/gql/". Dat is het formaat dat presets gebruiken, omdat ze meer dan één bestand schrijven.
Plugins bepalen wat er wordt gegenereerd. Codegen laadt het schema en de documenten, en elke plugin zet die om in een stuk output.
Dit zijn de plugins die je het vaakst ziet in TypeScript-projecten:
typescript-operations: Types voor je queries, mutaties, subscriptions en fragments, plus de enums en inputs die ze gebruiken
typed-document-node: Documentobjecten met de resultaat- en variabelentypes gekoppeld, die de meeste GraphQL-clients begrijpen
typescript: Types voor elk object, elke enum en input in het schema, of je operaties ze nu gebruiken of niet
typescript-resolvers: Typesignatures voor resolverfuncties aan de serverkant
Een preset is een bundel van plugins met een verstandige standaardconfig. De preset client uit de sectie Hoe het werkt is degene die The Guild aanraadt voor clientapps. Hij bouwt onder de motorkap voort op de verbeterde typescript-operations, zodat je de voordelen krijgt zonder extra setup.
Elke plugin heeft zijn eigen configopties, en dat zijn er veel. Je hoeft ze niet vooraf te leren. Begin met de defaults en zoek een optie op wanneer de gegenereerde output niet matcht met wat je code verwacht.
Wat je uit Codegen haalt, hangt af van de plugins die je kiest.
Hetzelfde schema en dezelfde operaties kunnen platte TypeScript-types opleveren of kant-en-klare clientcode. Dit zijn de outputs die je het vaakst gebruikt.
De plugin typescript genereert een type voor elk object, elke enum en input in je schema. Voor de cursuscatalogus ziet het type Course er zo uit:
export type Course = {
__typename?: 'Course';
durationHours: Scalars['Int']['output'];
id: Scalars['ID']['output'];
instructor: Instructor;
level: Level;
title: Scalars['String']['output'];
};
Deze types beschrijven het volledige schema, wat ze handig maakt aan de serverkant, in testfixtures en in utilityfuncties die met complete objecten werken.
Maar ze passen minder goed bij clientcode. Een type Course zegt dat elk veld bestaat, ook de velden waar je query niet om vroeg.
De plugin typescript-operations genereert twee types per operatie: één voor het resultaat en één voor de variabelen. Het werkt hetzelfde voor queries en subscriptions.
Dit genereert het voor een mutatie EnrollStudent:
export type EnrollStudentMutationVariables = Exact<{
courseId: string | number;
studentEmail: string;
}>;
export type EnrollStudentMutation = { enrollStudent: { id: string, course: { title: string } } };
Het resultatentype matcht de selectieset, veld voor veld. Het variabelentype is Exact, zodat TypeScript extra of verkeerd gespelde variabelen afwijst.
De variabele courseId accepteert zowel string als number omdat de GraphQL-scalar ID beide als input accepteert. Sinds v6 is het standaardtype string | number, wat het juiste type is voor clientgebruik.
Een gewone DocumentNode is een geparste GraphQL-query. Die kent de structuur van de query, maar weet niet wat de query teruggeeft of welke variabelen nodig zijn.
Een getypeerde document node heeft beide types erbij. De plugin typed-document-node genereert er één per operatie:
export const GetCoursesDocument = { /* ... */ } as unknown as DocumentNode<GetCoursesQuery, GetCoursesQueryVariables>;
Zo kan elke functie die een TypedDocumentNode accepteert het resultaat- en variabelentype afleiden uit alleen het document. Je geeft geen generics met de hand door, en je kunt geen query combineren met de verkeerde types.
De meeste moderne GraphQL-clients accepteren getypeerde document nodes, waaronder Apollo Client, urql en graphql-request. Je ziet alle drie later in dit artikel in actie.
Sommige plugins gaan verder en genereren code voor een specifieke client of framework. Bijvoorbeeld:
typescript-react-apollo: Genereert getypeerde React-hooks voor Apollo Client
typescript-urql: Genereert getypeerde hooks en componenten voor urql
typescript-react-query: Genereert getypeerde hooks voor TanStack Query
typescript-graphql-request: Genereert een getypte SDK met één functie per operatie
Dit zijn allemaal communityplugins. The Guild heeft ze verhuisd naar een aparte communityrepository, dus hun releasetempo verschilt van de coreplugins.
Je hebt ze ook minder vaak nodig dan vroeger. Wanneer een client getypeerde document nodes ondersteunt, voegen gegenereerde hooks weinig typesafety toe. De documentatie van Apollo raadt bijvoorbeeld aan om de plugin typescript-operations te gebruiken, die zich richt op alleen types genereren en geen extra runtimecode bevat.
In het algemeen begin je met operatietypes en getypeerde document nodes. Voeg daarna pas een clientspecifieke plugin toe als je team de gegenereerde hooks of SDK-functies wil.
Gegenereerde types zijn alleen nuttig als ze fouten vangen, dus laten we er een paar maken.
Dit voorbeeld breidt het cursuscatalogusproject uit met een tweede query, een kleine helper die die naar de API stuurt, en de code die het resultaat gebruikt. Het gaat uit van een GraphQL-server die het schema implementeert op http://localhost:4000/graphql.
Dit is het deel van het schema dat dit voorbeeld gebruikt:
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
}
Twee details zijn hier belangrijk. Het veld course retourneert Course zonder !, dus het is null wanneer geen cursus overeenkomt met het ID. De bio van de docent is ook nullable.
Maak src/operations/course.graphql met een query die één cursus ophaalt:
query GetCourse($id: ID!) {
course(id: $id) {
title
durationHours
instructor {
name
bio
}
}
}
Draai npm run codegen opnieuw. Codegen voegt twee nieuwe types toe aan src/generated/graphql.ts:

Inhoud van het bestand graphql.ts
De nullability uit het schema blijft behouden. course kan null zijn, bio kan null zijn, en id is vereist omdat de query die als ID! declareert.
Nu de applicatiecode. Maak src/execute.ts met een helper die elk getypt document naar de API stuurt:
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;
}
De helper weet niets over cursussen. Hij haalt beide types uit het document dat je doorgeeft, en de functie print() zet het document terug om in een querystring voor het request.
Gebruik hem vervolgens 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");
}
De functie formatCourse() haalt zijn parametertype uit het gegenereerde querytype. NonNullable verwijdert de null-case, omdat de functie pas draait na de if-check.
Het voorbeeld gebruikt top-level await, dus zet "type": "module" in package.json en draai het met npx tsx src/course-details.ts:

Geslaagde call naar een GraphQL-mockserver
Dit gaat er nu van uit dat je een GraphQL-server hebt draaien. Hier is een mockserver die ik gebruikte:
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");
});
Probeer het nu te breken. Open src/course-details.ts en vervang alles onder de functie formatCourse() door drie fouten die handgeschreven types vaak doorlaten:
// 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);
Typecheck vervolgens het project:
npx tsc -p .
TypeScript vangt alle drie voordat de code draait:

Fouten gedetecteerd door TypeScript
Zo reduceert Codegen mismatches tussen de frontend en de API. De types komen uit hetzelfde schema dat de server gebruikt, dus ze kunnen geen velden beschrijven die niet bestaan of nulls missen die de server kan teruggeven.
En de bescherming stopt niet na de eerste run. Stel dat het backendteam durationHours nullable maakt. Je regenereert, het type verandert naar number | null, en TypeScript wijst elke regel aan die van een number uitging.
Codegen verstuurt geen requests. Dat is de taak van je GraphQL-client.
De gegenereerde getypeerde document nodes zijn het overdrachtspunt tussen beide. Je geeft ze door aan de client, en de client leidt de types af. De voorbeelden hieronder gebruiken hetzelfde bestand src/generated/graphql.ts en de mock-API uit de vorige sectie, dus laat npx tsx mock/server.ts in een aparte terminal draaien.
Installeer Apollo Client en diens peer dependency rxjs:
npm install @apollo/client rxjs
Het voorbeeld draait ook de mutatie EnrollStudent. Sla die op als src/operations/enroll.graphql:
mutation EnrollStudent($courseId: ID!, $studentEmail: String!) {
enrollStudent(courseId: $courseId, studentEmail: $studentEmail) {
id
course {
title
}
}
}
De documentatie van Apollo raadt ook twee configopties voor Codegen aan, omdat Apollo Client altijd __typename-velden opneemt maar die niet toevoegt aan roottypes. Voeg ze toe aan het outputdoel in codegen.ts:
"src/generated/graphql.ts": {
plugins: ["typescript-operations", "typed-document-node"],
config: {
nonOptionalTypename: true,
skipTypeNameForRoot: true,
},
},
Met deze opties krijgt elk object in de gegenereerde resultatentypes een verplicht veld __typename, zoals __typename: 'Course'. Dat matcht wat de cache van Apollo teruggeeft.
Draai npm run codegen opnieuw om de nieuwe mutatie en config te detecteren.
Apollo Client accepteert getypeerde document nodes in query() en mutate(). Sla dit op als 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);
Het object data is getypt als GetCoursesQuery | undefined, en TypeScript controleert variables tegen GetCoursesQueryVariables. De mutatie werkt hetzelfde met EnrollStudentMutation en de bijbehorende variabelen.
Draai het met npx tsx src/apollo.ts:

Apollo-output
De eerste regel komt van de query, omdat dat de enige beginnerscursus is. De tweede komt van de mutatie, die de titel retourneert van de cursus waarvoor de student zich inschreef.
De React-hooks, zoals useQuery() en useMutation(), leiden types op dezelfde manier af uit getypeerde document nodes.
Installeer het corepakket van urql:
npm install @urql/core
urql werkt out of the box met getypeerde document nodes. Geef het document als eerste argument door aan 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));
Het tweede argument is getypt als de queryvariabelen, en result.data is getypt als het queryresultaat.
Draai het met npx tsx src/urql.ts:

Urql-output
De frameworkbindings, zoals useQuery() in het pakket urql voor React, accepteren ook getypeerde document nodes.
Je hebt geen volwaardige client nodig om getypte resultaten te krijgen.
De client graphql-request is een minimale client die één request verstuurt en de data teruggeeft. Installeer hem met het volgende commando:
npm install graphql-request
Hij leidt types af uit getypeerde document nodes net als Apollo en urql. Sla dit op als 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);
Draai het met npx tsx src/graphql-request.ts:

Output van GraphQL-request
Hetzelfde idee werkt met TanStack Query. Die stuurt zelf geen GraphQL-requests, dus je roept graphql-request aan of de helper execute() uit de vorige sectie binnen de queryfunctie, en het resultatentype loopt door naar je code.
En als je client helemaal geen getypeerde document nodes ondersteunt, kun je nog steeds de platte operatietypes gebruiken. Importeer GetCourseQuery en GetCourseQueryVariables en geef ze door als generics. Dat is wat meer handwerk, maar de types komen nog steeds uit het schema.
Gegenereerde types helpen alleen als ze overeenkomen met het huidige schema en de huidige operaties.
Als je vergeet Codegen opnieuw te draaien na een wijziging, beschrijven de types code die niet meer bestaat. Dat is hetzelfde driftprobleem als bij handgeschreven types, alleen met extra stappen. De oplossing is de handmatige stap uit de workflow halen.
Begin met een paar scripts in package.json, zodat iedereen in het team Codegen op dezelfde manier draait:
{
"scripts": {
"codegen": "graphql-codegen",
"codegen:watch": "graphql-codegen --watch",
"codegen:check": "graphql-codegen --check",
"prebuild": "npm run codegen",
"build": "tsc -p ."
}
}
De rest van deze sectie behandelt waar elk script voor is.
De watch-modus draait Codegen opnieuw telkens wanneer een gevolgd bestand wijzigt. Het heeft het package @parcel/watcher nodig, dat Codegen niet voor je installeert:
npm install -D @parcel/watcher
npm run codegen:watch
Als je de installatie overslaat, draait Codegen één keer en print het Failed to import @parcel/watcher in plaats van te watchen.
De watch-modus volgt zowel je operaties als een lokaal schemabestand. Als je een veld toevoegt aan course.graphql, verschijnt het nieuwe veld een seconde later in GetCourseQuery. Als je durationHours nullable maakt in schema.graphql, verandert het type in number | null, en je editor markeert elke regel die van een number uitging.
Laat het tijdens ontwikkeling draaien in een terminal naast je devserver.
npm draait een script genaamd prebuild vóór build. Met de bovenstaande scripts regenereert npm run build eerst de types en draait pas daarna de TypeScript-compiler.
Zo draait een build nooit tegen verouderde types, zelfs niet als iemand vergat Codegen te draaien na het pullen van wijzigingen.
De vlag --check draait Codegen in dry-runmodus. Het schrijft geen bestanden, vergelijkt wat het zou genereren met wat op schijf staat en sluit af met code 1 als er iets verschilt.
Hier is een minimale GitHub Actions-workflow die het gebruikt:
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
De stap codegen:check heeft alleen zin als je gegenereerde bestanden commit naar de repo. Zo niet, laat die stap dan weg, want npm run build regenereert alles toch via de hook prebuild.
Operaties wijzigen in je repo, dus watch-modus en prebuild dekken die af. Schemawijzigingen zijn lastiger, omdat die meestal van een ander team komen.
Hoe je ze afhandelt, hangt af van waar het schema staat:
codegen:check in CI faalt zodra het schema van de server niet meer matcht met je gecommitte outputDe URLooptie heeft één addertje. De watch-modus reageert op bestandswijzigingen, dus hij merkt niet wanneer een extern schema verandert. Je ziet die wijzigingen pas bij de volgende handmatige run of CI-job.
Beide benaderingen geven je TypeScript-types voor GraphQL-data, maar er zijn enkele verschillen om te kennen.
Met handgeschreven types lees je het schema en schrijf je zelf overeenkomende interfaces.
Dat geeft je volledige controle. Je kunt types noemen zoals jij wilt, een paar operaties samenvoegen tot één gedeeld type, of hulptyperingen toevoegen die het schema niet heeft.
Maar het is handwerk. Elk nieuw veld of hernoemd argument betekent dat je het bijbehorende type met de hand moet zoeken en bijwerken. En niets waarschuwt je als je er één mist.
Daar komt drift vandaan. De interface Course uit de introductie compileerde nog steeds nadat de backend title had hernoemd, omdat TypeScript je code alleen tegen je eigen types controleert, niet tegen de API.
Gegenereerde types komen uit hetzelfde schema dat de server gebruikt, dus ze zijn per ontwerp gesynchroniseerd met je GraphQL-definities. Een schemawijziging plus een Codegen-run werkt alle getroffen types in één keer bij.
Je schrijft ook veel minder code. Het cursuscatalogusproject heeft drie operaties en nul handgeschreven types daarvoor.
En je krijgt vrijwel gratis schema-gedreven ontwikkeling. Je wijzigt het schema of een operatie, regenereert, en de compiler geeft je een lijst van alles wat moet worden aangepast.
Maar onthoud: Codegen weerspiegelt alleen wat je erin stopt.
Als het schema elk veld als nullable markeert, zitten je gegenereerde types vol met | null-checks. Als een operatie 40 velden ophaalt terwijl de pagina er vijf nodig heeft, heeft het gegenereerde type 40 velden.
Hier is een korte samenvatting van de verschillen:
| Handgeschreven types | Gegenereerde types | |
|---|---|---|
| Onderhoud | Handmatig bij elke schemawijziging | Eén commando na elke wijziging |
| Schemadrift | Mogelijk en stil | Opgevangen bij generatie of compileertijd |
| Boilerplate | Groeit met elke operatie | Niets om met de hand te schrijven |
| Controle over aangepaste types | Volledig | Beperkt tot pluginconfigopties |
| Schema-gedreven ontwikkeling | Handmatig kruisen en controleren | Compiler toont elke getroffen regel |
Codegen vergeleken met handgeschreven types
De twee worden vaak door elkaar gehaald omdat Codegen vaak introspectie gebruikt. Toch lossen ze verschillende problemen op.
Introspectie is een functie van de GraphQL-specificatie. Elke GraphQL-server kan queries over zijn eigen schema beantwoorden via speciale velden zoals __schema en __type. Het resultaat is een JSON-beschrijving van elk type, veld en argument dat de API aanbiedt.
In gewone taal beantwoordt introspectie de vraag "Hoe ziet deze API eruit?"
Codegen beantwoordt de vraag "Welke code heeft mijn applicatie nodig?" Het neemt schema-informatie en je operaties, en schrijft TypeScript-types en getypeerde document nodes.
Wanneer je schema instelt op een URL in codegen.ts, verstuurt Codegen een introspectiequery naar die server om het schema op te halen. Met een lokaal .graphql-bestand gebeurt er helemaal geen introspectie.
Codegen kan ook een introspectieresultaat als output produceren. De plugin @graphql-codegen/introspection schrijft het schema als introspectie-JSON, wat sommige tools, zoals de Graphcache van urql, gebruiken voor schema-bewuste caching.
| Introspectie | GraphQL Codegen | |
|---|---|---|
| Wat het is | Ingebouwd GraphQL-queriesysteem | Commandlinetool en plugin |
| Input | Een query naar een draaiende server | Een schema en je operaties |
| Output | JSON-beschrijving van het schema | Types, document nodes, clientcode |
| Wanneer het draait | Wanneer een client de server vraagt | Tijdens ontwikkeling, builds en CI |
Codegen vergeleken met introspectie
De meeste Codegen-fouten komen neer op één van vier problemen.
Codegen draait zonder fouten, maar het type dat je verwacht staat niet in het outputbestand.
Controleer deze drie dingen in volgorde:
Schema: Als het type niet bestaat in het schema dat Codegen laadt, kan het niet worden gegenereerd, dus open het schemabestand of draai de introspectiequery zelf en bevestig dat het er is
Documents: Operatietypes bestaan alleen voor operaties die Codegen heeft gevonden, dus als het bestand met je query niet wordt gematcht door het globpatroon documents, ontbreken de types
Plugins: Sinds v6 genereert typescript-operations alleen de enums en inputs die je operaties gebruiken, dus als je een volledig schematype zoals Course nodig hebt, voeg de plugin typescript toe aan een apart outputbestand
Als je een plugin gebruikt die je niet hebt geïnstalleerd, faalt Codegen met een duidelijke melding:
Unable to find template plugin matching 'typescript-react-apollo'
Install one of the following packages:
- @graphql-codegen/typescript-react-apollo
Deze fout betekent dat het globpatroon documents geen enkel bestand met een operatie heeft gematcht:
Unable to find any GraphQL type definitions for the following pointers:
- src/**/*.gql
Controleer of de extensie in het globpatroon overeenkomt met je bestanden. Een patroon .gql vindt geen .graphql-bestanden, en een patroon src/**/*.graphql vindt geen operaties inline in .ts-bestanden.
Controleer voor inline operaties ook de tag. Codegen vindt alleen template literals die zijn omwikkeld met gql of gemarkeerd met een /* GraphQL */-commentaar. Een gewone template literal met een query erin is onzichtbaar.
Als je ignoreNoDocuments: true instelt, toont Codegen deze fout niet en genereert het een output zonder operatietypes. Die optie is handig in een gloednieuw project, maar kan later een kapot globpatroon verbergen.
De types compileren, maar weerspiegelen niet de laatste wijziging in een operatie of het schema.
Codegen draait niet uit zichzelf. Draai het opnieuw na elke wijziging aan een .graphql-bestand, een inline operatie, het schema of codegen.ts. Tijdens ontwikkeling doet npm run codegen:watch dat voor je.
Als de types in CI verouderd zijn, commit de geregenereerde bestanden of laat de hook prebuild ze opnieuw genereren vóór de build.
De types compileren, maar de data van de server heeft op runtime een andere vorm.
Dat betekent bijna altijd dat Codegen en je app met verschillende schema’s praten. Bijvoorbeeld: Codegen leest een lokaal schema.graphql dat een week oud is, terwijl de app een productie-API aanroept die sindsdien is veranderd.
Controleer waar de sleutel schema naar wijst. Als het een bestand is, vergelijk het met de API die je app aanroept en werk het bij. Als het een URL is, zorg dat het dezelfde omgeving is die je app gebruikt, niet een stagingserver met een nieuwer of ouder schema.
Genereer vervolgens opnieuw en bekijk de diff. Als een operatief bestand veranderd is maar het gegenereerde bestand niet, staat de operatie waarschijnlijk buiten het globpatroon documents.
Codegen heeft niet veel configuratie nodig om goed te werken. Deze practices houden het zo wanneer het project groeit:
Houd het schema en de gegenereerde bestanden in sync: Draai de watch-modus tijdens ontwikkeling en prebuild vóór builds, zodat niemand met types van een ouder schema werkt
Genereer operatiespecifieke types: Gebruik typescript-operations voor clientcode omdat die exact matchen wat elke query ophaalt
Commit gegenereerde bestanden alleen als je workflow ze nodig heeft: Gecommitte bestanden laten je typewijzigingen reviewen in pull requests en --check draaien in CI, en als je team dat niet nodig heeft, voeg de outputmap toe aan .gitignore en genereer bij elke build
Houd de config kort: Eén outputbestand met twee plugins dekt de meeste clientprojecten; elke extra optie is weer iets dat de volgende developer moet begrijpen voor hij iets aanpast
Draai Codegen in CI bij elke push: Een stap --check of een hook prebuild vangt de wijzigingen op die iemand lokaal vergat te regenereren
Bewerk nooit gegenereerde bestanden met de hand: De volgende Codegen-run overschrijft je wijzigingen, dus fix het schema, de operatie of de config in plaats daarvan
GraphQL Codegen neemt het meest repetitieve deel van GraphQL-ontwikkeling over: schema’s en operaties met de hand omzetten in TypeScript-types.
De workflow blijft hetzelfde, hoe groot het project ook wordt. Je definieert het schema en de operaties, configureert Codegen, genereert de types en getypeerde document nodes, en importeert ze in je applicatiecode.
De grootste waarde is dat je types altijd de API beschrijven die je daadwerkelijk aanroept, niet de code die je niet hoeft te schrijven. Wanneer het schema verandert, vertelt de compiler je of er iets stuk is gegaan zodat je fouten vroeg kunt vangen.
Wil je leren hoe GraphQL wordt gebruikt in een moderne NoSQL-database? Lees dan onze blogpost MongoDB en GraphQL.
GraphQL Codegen genereert code op basis van een GraphQL-schema en je GraphQL-operaties. De meeste teams gebruiken het om TypeScript-types en getypeerde document nodes voor hun queries en mutaties te krijgen, zodat ze die types niet met de hand hoeven te schrijven en bij te werken. Het kan ook resolvertype’s voor servers genereren en kant-en-klare hooks voor specifieke clients.
Nee. Codegen kan het schema lezen uit een lokaal .graphql-bestand, dus het werkt zonder server. Je hebt alleen een draaiende server nodig als je de sleutel schema naar een URL wijst, omdat Codegen dan het schema via introspectie ophaalt.
Dat hangt af van de workflow van je team. Gecommitte bestanden laten je typewijzigingen reviewen in pull requests en graphql-codegen --check gebruiken in CI om verouderde output te detecteren. Als je dat niet nodig hebt, voeg de outputmap toe aan .gitignore en regenereer de bestanden vóór elke build.
The Guild raadt de clientpreset aan voor de meeste clientapps, omdat die in één setup operatietypes, getypeerde document nodes en fragmentmasking bundelt. Apollo raadt in plaats daarvan de plugin typescript-operations aan, omdat de preset runtimecode en features toevoegt die conflicteren met Apollo Client. Gebruik je Apollo, ga dan voor de plugins. Anders is de preset een goede standaard.
Sinds v6 genereert de plugin typescript-operations zijn eigen enum- en inputtypes. Als je config nog steeds zowel typescript als typescript-operations voor hetzelfde outputbestand vermeldt, declareren beide plugins types zoals Level, en gooit TypeScript fouten over dubbele declaraties. Verwijder de plugin typescript uit dat outputbestand, of verplaats hem naar een apart bestand als je de volledige schematypes nog nodig hebt.
Leer met DataCamp
Cursus
Leerpad
Leerpad