Sari la conținutul principal

GraphQL Codegen: Cum generezi cod tip-safe dintr-o schemă GraphQL

Un ghid practic pentru GraphQL Code Generator care acoperă setarea și configurarea, tipurile TypeScript și documentele tipizate generate, integrarea cu Apollo, urql și graphql-request și automatizarea cu modul watch și CI.
Actualizat 2 oct. 2026  · 12 min. citește

Descoperă cu AI

ChatGPTClaudePerplexity

Tipurile TypeScript scrise manual pentru un API GraphQL devin învechite în momentul în care cineva schimbă schema.

Să zicem că un developer backend redenumește câmpul title de pe tipul Course în name. Frontend-ul încă se compilează pentru că interfața Course scrisă manual n-are idee că s-a schimbat ceva, așa că bug-ul apare doar la runtime. Aplicațiile reale tind să aibă zeci de interogări, mutații și fragmente, deci poți vedea cât de repede poate escalada.

GraphQL Code Generator, sau pe scurt GraphQL Codegen, citește schema și operațiile tale GraphQL și le transformă în tipuri, ajutoare pentru interogări, integrări pentru clienți și alt boilerplate pe care altfel l-ai scrie de mână. Este folosit cel mai des cu TypeScript și clienți populari precum Apollo și urql. O singură comandă readuce tipurile în sincron ori de câte ori se schimbă schema.

În acest articol te ghidez prin configurarea GraphQL Codegen într-un proiect mic TypeScript, cum îl configurezi, cum folosești tipurile generate și cum îl rulezi automat ca parte din fluxul tău de lucru.

Cum se compară GraphQL cu REST? Citește Graph QL vs REST: Ghid complet ca să înveți diferențele și avantajele.

Ce este GraphQL Codegen?

GraphQL Codegen este un instrument de linie de comandă care transformă definițiile GraphQL în cod de aplicație.

Este un proiect open-source de la The Guild și îl instalezi ca dependență de dezvoltare în proiectul tău. Leagă trei lucruri:

  • Schema GraphQL: Contractul dintre server și clienții săi, cu fiecare tip, câmp și argument expus de API
  • Operațiile GraphQL: Interogările, mutațiile, abonamentele și fragmentele pe care aplicația ta le trimite către API
  • Codul generat: Tipurile și helper-ele pe care Codegen le scrie pe baza schemei și a operațiilor tale

Schema îi spune lui Codegen ce e posibil, iar operațiile îi spun ce cere aplicația ta.

Codegen combină ambele, astfel încât fiecare tip generat să corespundă exact formei răspunsului pentru o operație. De asemenea, verifică fiecare operație față de schemă. Câmpul title redenumit din introducere ar opri generarea cu o eroare de tipul Cannot query field "title" on type "Course", pe care ai prinde-o înainte ca codul să ruleze.

Output-ul depinde de pluginurile și configurația ta. Codegen în sine nu scrie TypeScript. Pluginurile o fac. De exemplu, pluginul typescript generează tipuri pentru schemă, iar pluginul typescript-operations generează tipuri pentru interogări și mutații. Există pluginuri și pentru alte limbaje, precum Java și C#, dar acest articol rămâne la TypeScript.

Un lucru de reținut este că Codegen nu înlocuiește serverul tău GraphQL.

Nu rezolvă interogări și nu aduce date. Citește definițiile GraphQL în timpul dezvoltării și scrie fișiere. Serverul tău și clientul GraphQL fac restul.

Cum funcționează GraphQL Codegen

Fiecare setare Codegen urmează același flux: schemă + operații GraphQL -> configurație Codegen -> cod generat.

Diagram of the GraphQL Codegen workflow

Diagrama fluxului de lucru GraphQL Codegen

Iată ideea generală:

  1. Furnizează o schemă: Indică lui Codegen un fișier local .graphql sau un endpoint GraphQL în execuție, pe care Codegen îl citește prin introspecție

  2. Indică lui Codegen operațiile tale: Folosește patternuri glob precum src/**/*.ts pentru ca Codegen să găsească interogări, mutații și fragmente în codul tău

  3. Configurează pluginuri și ținte de output: Spune-i lui Codegen ce fișiere să scrie și ce pluginuri să genereze fiecare fișier

  4. Rulează Codegen: Rulează npx graphql-codegen pentru a încărca schema, valida operațiile și scrie output-ul

  5. Folosește artefactele generate: Importează tipurile și documentele tipizate generate în codul aplicației tale

Pașii 1-3 se pun într-un singur fișier de configurare, de obicei codegen.ts. Iată cea mai mică variantă pentru un proiect de catalog de cursuri:

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;

Presetul client este un pachet de pluginuri care generează dintr-un foc tipurile pentru schemă, tipurile pentru operații și documente tipizate. Voi acoperi fiecare în secțiunile de setare și configurare.

Cum setezi GraphQL Codegen

Cel mai bun mod de a înțelege Codegen este să îl rulezi pe un proiect real.

În această secțiune vei construi de la zero un mic proiect de catalog de cursuri. Are o schemă cu cursuri și instructori, o interogare și o configurație Codegen care transformă interogarea în tipuri TypeScript. Tot ce îți trebuie este Node.js și npm.

Pasul 1: Instalează GraphQL Code Generator

Creează un folder nou și inițializează un proiect npm:

mkdir course-catalog && cd course-catalog
npm init -y

Codegen are nevoie de câteva pachete. Două merg în dependențe obișnuite, iar restul în dependențe de dezvoltare pentru că rulează doar în timpul dezvoltării:

npm install graphql @graphql-typed-document-node/core
npm install -D typescript @graphql-codegen/cli @graphql-codegen/typescript-operations @graphql-codegen/typed-document-node

Iată ce face fiecare pachet:

  • graphql: Implementarea de referință GraphQL pentru JavaScript, pe care Codegen o folosește pentru a parsa schema și operațiile

  • @graphql-typed-document-node/core: Furnizează tipul TypedDocumentNode pe care îl importă codul generat

  • @graphql-codegen/cli: Instrumentul de linie de comandă Codegen

  • @graphql-codegen/typescript-operations: Pluginul care generează tipuri pentru interogări, mutații și fragmente

  • @graphql-codegen/typed-document-node: Pluginul care generează obiecte document cu tipurile atașate

Pasul 2: Creează un fișier de configurare

Codegen își citește setările dintr-un fișier de configurare în rădăcina proiectului. Creează codegen.ts cu o configurare goală deocamdată:

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

const config: CodegenConfig = {};

export default config;

Poți rula și npx graphql-codegen init pentru a răspunde la câteva întrebări și a obține un fișier de configurare generat. Îl scriu manual aici ca să vezi ce face fiecare setare.

Pasul 3: Specifică locațiile pentru schemă și documente

Codegen are nevoie de două inputuri, așa că hai să le creăm pe ambele.

Mai întâi, salvează schema ca schema.graphql în rădăcina proiectului:

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!
}

Apoi, creează src/operations/courses.graphql cu o interogare care aduce cursurile și instructorii lor:

query GetCourses($level: Level, $limit: Int) {
  courses(level: $level, limit: $limit) {
    id
    title
    level
    instructor {
      name
    }
  }
}

Acum indică-le lui Codegen ambele fișiere. Cheia schema ia calea către schemă, iar documents ia un glob care potrivește operațiile tale:

const config: CodegenConfig = {
  schema: "schema.graphql",
  documents: ["src/**/*.graphql"],
};

Pasul 4: Alege un fișier de output

Cheia generates îi spune lui Codegen unde să scrie output-ul. Fiecare cheie din interior este o cale de fișier:

const config: CodegenConfig = {
  schema: "schema.graphql",
  documents: ["src/**/*.graphql"],
  generates: {
    "src/generated/graphql.ts": {},
  },
};

Codegen creează folderul src/generated/ dacă nu există.

Pasul 5: Adaugă pluginurile relevante

Un fișier de output fără pluginuri este gol. Adaugă cele două pluginuri pe care le-ai instalat:

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;

Dacă ai urmat un tutorial mai vechi, probabil ai văzut și pluginul typescript în această listă. Îl poți elimina acum, deoarece typescript-operations funcționează singur și generează doar tipurile Input, Enum și pentru Operații care sunt efectiv folosite. Dacă le pui pe ambele în același fișier de output în versiunile curente, TypeScript aruncă erori de declarații duplicate.

Pasul 6: Rulează Codegen

Adaugă un script npm ca să nu trebuiască să-ți amintești comanda:

npm pkg set scripts.codegen="graphql-codegen"

Apoi rulează-l:

npm run codegen

Codegen încarcă schema, validează interogarea față de aceasta și scrie output-ul:

Codegen generation results

Rezultatele generării Codegen

Dacă o operație face referire la un câmp care nu există în schemă, aici e locul unde rularea eșuează și nu se scrie niciun fișier.

Pasul 7: Inspectează output-ul generat

Deschide src/generated/graphql.ts. Iată ce vei vedea:

Output-ul generat

Codegen a generat patru lucruri:

  • Level: O uniune de stringuri pentru enum, generată doar pentru că interogarea o folosește

  • GetCoursesQueryVariables: Tipul pentru variabilele pe care le transmiți interogării, unde ambele sunt opționale pentru că schema nu le marchează drept obligatorii

  • GetCoursesQuery: Forma răspunsului, doar cu câmpurile pe care le cere interogarea

  • GetCoursesDocument: Interogarea parsată ca document tipizat, cu tipurile pentru rezultat și variabile atașate

Privește din nou GetCoursesQuery. Nu include durationHours sau bio al instructorului, pentru că interogarea nu le solicită. Dacă încerci să citești course.durationHours în codul tău, vei primi o eroare la compilare, nu un undefined la runtime.

Și asta e toată setarea — tu scrii GraphQL, iar Codegen scrie TypeScript-ul.

Cum configurezi GraphQL Codegen

Majoritatea configurărilor Codegen pe care le vei vedea sunt mai lungi decât cea de mai sus, dar sunt construite din aceleași patru setări.

Fișierul de configurare poate fi codegen.ts, codegen.yml sau codegen.json. Folosesc TypeScript pentru că ai autocomplete și verificări de tip chiar pe configurație. Setările sunt aceleași în orice format.

Schema

Cheia schema îi spune lui Codegen unde se află schema ta. Acceptă câteva tipuri de surse.

Un fișier local sau un glob funcționează când schema este în repo-ul tău:

schema: "schema.graphql",
// or, for a schema split across files
schema: "schema/**/*.graphql",

Un URL funcționează când schema e pe un server în execuție. Codegen trimite o interogare de introspecție către endpoint, iar tu poți transmite headere pentru autentificare:

schema: [
  {
    "https://api.example.com/graphql": {
      headers: { Authorization: Bearer ${process.env.API_TOKEN} },
    },
  },
],

Opțiunea URL funcționează doar dacă serverul are introspecția activată. Multe API-uri de producție o dezactivează, deci pentru CI, un fișier de schemă comis în repo este de obicei alegerea mai sigură.

Documente

Cheia documents îi spune lui Codegen unde să găsească interogările, mutațiile, abonamentele și fragmentele tale.

Operațiile pot fi în fișiere .graphql sau inline în codul tău TypeScript. Codegen găsește operațiile inline când sunt învelite într-un tag gql sau marcate cu un comentariu /* GraphQL */:

const EnrollStudent = /* GraphQL */ 
  mutation EnrollStudent($courseId: ID!, $studentEmail: String!) {
    enrollStudent(courseId: $courseId, studentEmail: $studentEmail) {
      id
      course {
        title
      }
    }
  }
;

Pentru a scana ambele tipuri de fișiere, lărgește globul. Adaugă un pattern negat ca să nu scaneze output-ul propriu al Codegen:

documents: ["src/**/*.{graphql,ts}", "!src/generated/**"],

Dă fiecărei operații un nume unic. Codegen construiește nume de tipuri din numele operațiilor, astfel EnrollStudent devine EnrollStudentMutation și EnrollStudentMutationVariables.

Generates

Cheia generates map-ează căile de output la ceea ce intră în ele. Poți avea câte ținte de output ai nevoie, fiecare cu propriile pluginuri.

Să zicem că vrei tipurile pentru operații într-un fișier și tipurile complete ale schemei în altul:

generates: {
  "src/generated/graphql.ts": {
    plugins: ["typescript-operations", "typed-document-node"],
    config: {
      useTypeImports: true,
    },
  },
  "src/generated/schema-types.ts": {
    plugins: ["typescript"],
  },
},

Obiectul config dintr-o țintă de output se aplică doar acelui fișier. Aici, useTypeImports face ca fișierul generat să folosească import type, lucru necesar în unele setup-uri de build. Dacă pui config la rădăcină, se aplică tuturor output-urilor.

O cale de output poate fi și un folder care se termină cu slash, precum "src/gql/". Asta folosesc preset-urile, deoarece scriu mai multe fișiere.

Pluginuri

Pluginurile decid ce se generează. Codegen încarcă schema și documentele, iar fiecare plugin le transformă într-o bucată de output.

Acestea sunt pluginurile pe care le vei vedea cel mai des în proiectele TypeScript:

  • typescript-operations: Tipuri pentru interogările, mutațiile, abonamentele și fragmentele tale, plus enumurile și inputurile pe care le folosesc

  • typed-document-node: Obiecte document cu tipurile pentru rezultat și variabile atașate, pe care majoritatea clienților GraphQL le înțeleg

  • typescript: Tipuri pentru fiecare obiect, enum și input din schemă, fie că operațiile tale le folosesc sau nu

  • typescript-resolvers: Semnături de tip pentru funcțiile resolver pe partea de server

Un preset este un pachet de pluginuri cu o configurare implicită rezonabilă. Presetul client din secțiunea Cum funcționează este cel recomandat de The Guild pentru aplicațiile client. Se bazează pe typescript-operations îmbunătățit, așa că obții beneficiile lui fără setup suplimentar.

Fiecare plugin are propriile opțiuni de configurare, și sunt multe. Nu trebuie să le înveți dinainte. Începe cu valorile implicite și caută o opțiune când output-ul generat nu se potrivește cu ce așteaptă codul tău.

Ce poate genera GraphQL Codegen?

Ce obții de la Codegen depinde de pluginurile pe care le alegi.

Aceeași schemă și aceleași operații pot produce tipuri TypeScript simple sau cod de client gata de folosit. Iată output-urile pe care le vei folosi cel mai des.

Tipuri TypeScript

Pluginul typescript generează un tip pentru fiecare obiect, enum și input din schema ta. Pentru catalogul de cursuri, tipul Course arată așa:

export type Course = {
  __typename?: 'Course';
  durationHours: Scalars['Int']['output'];
  id: Scalars['ID']['output'];
  instructor: Instructor;
  level: Level;
  title: Scalars['String']['output'];
};

Aceste tipuri descriu schema completă, ceea ce le face utile pe partea de server, în fixture-uri de test și în funcții utilitare care lucrează cu obiecte complete.

Dar se potrivesc mai puțin pentru codul de client. Un tip Course spune că fiecare câmp există, chiar și cele pe care interogarea ta nu le-a cerut niciodată.

Tipuri pentru operații

Pluginul typescript-operations generează două tipuri per operație: unul pentru rezultat și unul pentru variabile. Funcționează la fel pentru interogări și abonamente.

Iată ce generează pentru o mutație EnrollStudent:

export type EnrollStudentMutationVariables = Exact<{
  courseId: string | number;
  studentEmail: string;
}>;

export type EnrollStudentMutation = { enrollStudent: { id: string, course: { title: string } } };

Tipul rezultatului corespunde setului de selecție, câmp cu câmp. Tipul variabilelor este Exact, astfel încât TypeScript respinge variabilele în plus sau greșit scrise.

Variabila courseId acceptă atât string, cât și number pentru că scalarul ID din GraphQL acceptă ambele ca input. Începând cu v6, tipul implicit este string | number, care este tipul corect pentru cazurile de client.

Documente tipizate

Un DocumentNode obișnuit este o interogare GraphQL parsată. Îi cunoaște structura, dar nu are idee ce returnează interogarea sau ce variabile primește.

Un document tipizat are ambele tipuri împreună cu el. Pluginul typed-document-node generează câte unul per operație:

export const GetCoursesDocument = { /* ... */ } as unknown as DocumentNode<GetCoursesQuery, GetCoursesQueryVariables>;

Astfel, orice funcție care acceptă un TypedDocumentNode poate deduce tipurile pentru rezultat și variabile doar din document. Nu mai transmiți generice manual și nu poți combina o interogare cu tipurile greșite.

Majoritatea clienților GraphQL moderni acceptă documente tipizate, inclusiv Apollo Client, urql și graphql-request. Le vei vedea pe toate trei în acțiune mai târziu în acest articol.

Integrări specifice clientului

Unele pluginuri merg mai departe și generează cod pentru un client sau framework specific. De exemplu:

  • typescript-react-apollo: Generează hook-uri React tipizate pentru Apollo Client

  • typescript-urql: Generează hook-uri și componente tipizate pentru urql

  • typescript-react-query: Generează hook-uri tipizate pentru TanStack Query

  • typescript-graphql-request: Generează un SDK tipizat cu o funcție per operație

Toate acestea sunt pluginuri ale comunității. The Guild le-a mutat într-un repository separat al comunității, așa că ritmul lor de release e diferit față de pluginurile de bază.

De asemenea, ai nevoie de ele mai rar decât înainte. Când un client acceptă documente tipizate, hook-urile generate nu adaugă prea multă siguranță de tip. Documentația Apollo, de exemplu, recomandă folosirea pluginului typescript-operations, care se concentrează doar pe generarea de tipuri și nu include cod de runtime suplimentar.

În general, ar trebui să începi cu tipuri pentru operații și documente tipizate. Apoi, adaugă un plugin specific clientului doar dacă echipa ta vrea hook-urile sau funcțiile SDK generate.

GraphQL Codegen cu TypeScript

Tipurile generate contează doar dacă prind greșelile, așa că hai să facem câteva.

Acest exemplu extinde proiectul de catalog de cursuri cu o a doua interogare, un mic helper care o trimite către API și codul care folosește rezultatul. Presupune un server GraphQL care implementează schema și rulează la http://localhost:4000/graphql.

Iată partea din schemă folosită în acest exemplu:

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
}

Două detalii contează aici. Câmpul course returnează Course fără !, deci este null când niciun curs nu se potrivește ID-ului. bio al instructorului este și el nulabil.

Creează src/operations/course.graphql cu o interogare care aduce un singur curs:

query GetCourse($id: ID!) {
  course(id: $id) {
    title
    durationHours
    instructor {
      name
      bio
    }
  }
}

Rulează din nou npm run codegen. Codegen adaugă două tipuri noi în src/generated/graphql.ts:

Conținutul fișierului graphql.ts

Nulabilitatea din schemă se păstrează. course poate fi null, bio poate fi null, iar id este obligatoriu deoarece interogarea îl declară drept ID!.

Acum pentru codul aplicației. Creează src/execute.ts cu un helper care trimite orice document tipizat către 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;
}

Helper-ul nu știe nimic despre cursuri. Primește ambele tipuri din documentul pe care îl transmiți, iar funcția print() transformă documentul înapoi într-un string de interogare pentru request.

Apoi folosește-l în 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");
}

Funcția formatCourse() își ia tipul parametrului din tipul de interogare generat. NonNullable elimină cazul null, fiindcă funcția rulează doar după verificarea din if.

Exemplul folosește await la nivel de top, așa că setează "type": "module" în package.json și rulează-l cu npx tsx src/course-details.ts:

Successful call to a GraphQL mock server

Apel reușit către un server GraphQL mock

Acum, asta presupune că ai un server GraphQL care rulează. Iată unul mock pe care l-am folosit:

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");
});

Acum încearcă să-l strici. Deschide src/course-details.ts și înlocuiește totul sub funcția formatCourse() cu trei greșeli pe care tipurile scrise manual le lasă adesea să treacă:

// 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);

Apoi rulează type-check pe proiect:

npx tsc -p .

TypeScript le prinde pe toate trei înainte ca codul să ruleze:

Errors catched by TypeScript

Erori prinse de TypeScript

Așa reduce Codegen nepotrivirile dintre frontend și API. Tipurile vin din aceeași schemă pe care o folosește serverul, deci nu pot descrie câmpuri care nu există sau rata nulități pe care serverul le poate returna.

Iar protecția nu se oprește după prima rulare. Să zicem că echipa de backend face durationHours nulabil. Regenerzi, tipul devine number | null, iar TypeScript îți arată fiecare linie care presupunea un număr.

GraphQL Codegen cu clienți GraphQL

Codegen nu trimite requesturi. Asta e treaba clientului tău GraphQL.

Documentele tipizate generate sunt punctul de predare între cele două. Le transmiți clientului, iar clientul deduce tipurile. Exemplele de mai jos folosesc același fișier src/generated/graphql.ts și API-ul mock din secțiunea anterioară, așa că ține npx tsx mock/server.ts rulând într-un terminal separat.

GraphQL Codegen cu Apollo

Instalează Apollo Client și peer dependency-ul său rxjs:

npm install @apollo/client rxjs

Exemplul rulează și mutația EnrollStudent. Salveaz-o ca src/operations/enroll.graphql:

mutation EnrollStudent($courseId: ID!, $studentEmail: String!) {
  enrollStudent(courseId: $courseId, studentEmail: $studentEmail) {
    id
    course {
      title
    }
  }
}

Documentația Apollo recomandă și două opțiuni de configurare pentru Codegen, deoarece Apollo Client include mereu câmpuri __typename, dar nu le adaugă pe tipurile rădăcină. Adaugă-le în ținta de output din codegen.ts:

"src/generated/graphql.ts": {
  plugins: ["typescript-operations", "typed-document-node"],
  config: {
    nonOptionalTypename: true,
    skipTypeNameForRoot: true,
  },
},

Cu aceste opțiuni, fiecare obiect din tipurile de rezultat generate primește un câmp __typename obligatoriu, precum __typename: 'Course'. Asta se potrivește cu ce returnează cache-ul Apollo.

Rulează din nou npm run codegen pentru a detecta noua mutație și configurația.

Apollo Client acceptă documente tipizate în query() și mutate(). Salvează asta ca 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);

Obiectul data este tipizat ca GetCoursesQuery | undefined, iar TypeScript verifică variables față de GetCoursesQueryVariables. Mutația funcționează la fel cu EnrollStudentMutation și variabilele ei.

Rulează cu npx tsx src/apollo.ts:

Apollo output

Output Apollo

Primul rând vine din interogare, deoarece este singurul curs pentru începători. Al doilea vine din mutație, care întoarce titlul cursului în care s-a înscris studentul.

Hook-urile React, precum useQuery() și useMutation(), deduc tipurile din documentele tipizate în același mod.

GraphQL Codegen cu urql

Instalează pachetul de bază urql:

npm install @urql/core

urql funcționează cu documente tipizate out of the box. Transmite documentul ca primul argument la query() în 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));

Al doilea argument e tipizat ca variabilele interogării, iar result.data este tipizat ca rezultatul interogării.

Rulează cu npx tsx src/urql.ts:

Urql output

Output urql

Legăturile pentru framework, precum useQuery() din pachetul urql pentru React, acceptă și ele documente tipizate.

GraphQL Codegen cu alți clienți

Nu ai nevoie de un client complet ca să obții rezultate tipizate.

graphql-request este un client minimal care trimite un singur request și întoarce datele. Instalează-l cu comanda de mai jos:

npm install graphql-request

Deducerea tipurilor din documente tipizate funcționează la fel ca în Apollo și urql. Salvează asta ca 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);

Rulează cu npx tsx src/graphql-request.ts:

GraphQL request output

Output graphql-request

Aceeași idee funcționează cu TanStack Query. Nu trimite singur requesturi GraphQL, așa că apelezi graphql-request sau helper-ul execute() din secțiunea anterioară în interiorul funcției de interogare, iar tipul rezultatului curge mai departe în codul tău.

Iar dacă clientul tău nu suportă deloc documente tipizate, poți folosi în continuare tipurile simple pentru operații. Importează GetCourseQuery și GetCourseQueryVariables și transmite-le ca generice. Asta înseamnă mai multă muncă manuală, dar tipurile tot vin din schemă.

Cum rulezi GraphQL Codegen automat

Tipurile generate ajută doar dacă se potrivesc cu schema și operațiile curente.

Dacă uiți să rulezi din nou Codegen după o schimbare, tipurile descriu un cod care nu mai există. E aceeași problemă de drift ca la tipurile scrise manual, doar cu pași în plus. Soluția e să scoți pasul manual din flux.

Scripturi npm

Începe cu câteva scripturi în package.json, ca toată echipa să ruleze Codegen la fel:

{
  "scripts": {
    "codegen": "graphql-codegen",
    "codegen:watch": "graphql-codegen --watch",
    "codegen:check": "graphql-codegen --check",
    "prebuild": "npm run codegen",
    "build": "tsc -p ."
  }
}

Restul acestei secțiuni acoperă la ce folosește fiecare.

Modul de watch

Modul watch rulează din nou Codegen de fiecare dată când un fișier urmărit se schimbă. Are nevoie de pachetul @parcel/watcher, pe care Codegen nu îl instalează pentru tine:

npm install -D @parcel/watcher
npm run codegen:watch

Dacă sari peste instalare, Codegen rulează o dată și afișează Failed to import @parcel/watcher în loc să urmărească.

Modul watch urmărește atât operațiile tale, cât și un fișier de schemă local. Dacă adaugi un câmp în course.graphql, noul câmp apare în GetCourseQuery după o secundă. Dacă faci durationHours nulabil în schema.graphql, tipul devine number | null, iar editorul îți marchează fiecare linie care presupunea un număr.

Ține-l rulând într-un terminal lângă serverul tău de dezvoltare.

Generează înainte de build-uri

npm rulează un script numit prebuild înainte de build. Cu scripturile de mai sus, npm run build regenerează mai întâi tipurile și abia apoi rulează compilatorul TypeScript.

Astfel, un build nu rulează niciodată împotriva unor tipuri învechite, chiar dacă cineva a uitat să ruleze Codegen după ce a preluat schimbări.

CI/CD

Flag-ul --check rulează Codegen în mod dry-run. Nu scrie fișiere, compară ce ar genera cu ce e pe disk și iese cu codul 1 dacă diferă ceva.

Iată un workflow minimal GitHub Actions care îl folosește:

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

Pasul codegen:check are sens doar dacă comiți fișierele generate în repo. Dacă nu, renunță la pas, pentru că npm run build regenerează tot prin hook-ul prebuild oricum.

Regenerează atunci când se schimbă schema

Operațiile se schimbă în repo-ul tău, deci modul watch și prebuild le acoperă. Schimbările de schemă sunt mai complicate, pentru că de obicei vin de la altă echipă.

Cum le gestionezi depinde de unde e schema:

  • Fișier de schemă în același repo: Modul watch și CI prind schimbările; acesta e setup-ul cel mai ușor de folosit
  • Schema dintr-un URL: Codegen aduce cea mai recentă versiune la fiecare rulare, deci codegen:check în CI eșuează de îndată ce schema serverului nu mai corespunde cu output-ul tău comis
  • Schema publicată de alt repo: Copiaz-o sau descarc-o ca parte a build-ului, apoi rulează Codegen pe copia locală

Opțiunea URL are un neajuns. Modul watch reacționează la schimbări de fișiere, deci nu observă când se schimbă o schemă de la distanță. Vei vedea acele schimbări abia la următoarea rulare manuală sau în job-ul de CI.

GraphQL Codegen vs tipuri scrise manual

Ambele abordări îți oferă tipuri TypeScript pentru date GraphQL, dar există câteva diferențe de care ar trebui să ții cont.

Tipuri scrise manual

Cu tipuri scrise manual, citești schema și scrii tu însuți interfețe care se potrivesc.

Asta îți oferă control total. Poți denumi tipurile cum vrei, poți îmbina câteva operații într-un tip comun sau poți adăuga tipuri helper pe care schema nu le are.

Dar este muncă manuală. Fiecare câmp nou sau argument redenumit înseamnă să găsești și să actualizezi manual tipul corespunzător. Și nimic nu te avertizează dacă ratezi unul.

Acolo apare drift-ul. Interfața Course din introducere încă s-a compilat după ce backend-ul a redenumit title, pentru că TypeScript îți verifică doar codul față de tipurile tale, nu față de API.

Tipuri generate

Tipurile generate vin din aceeași schemă pe care o folosește serverul, așa că sunt sincronizate cu definițiile tale GraphQL prin design. O schimbare în schemă plus o rulare Codegen actualizează dintr-odată fiecare tip afectat.

Scrii și mult mai puțin cod. Proiectul de catalog de cursuri are trei operații și zero tipuri scrise manual pentru oricare dintre ele.

Și obții dezvoltare condusă de schemă aproape gratuit. Schimbi schema sau o operație, regenerezi, iar compilatorul îți dă o listă cu tot ce trebuie modificat.

Dar ține minte că Codegen reflectă doar ceea ce îi dai.

Dacă schema marchează fiecare câmp ca nulabil, tipurile generate sunt pline de | null. Dacă o operație aduce 40 de câmpuri când pagina are nevoie de cinci, tipul generat are 40 de câmpuri.

Iată un rezumat rapid al diferențelor:

  Tipuri scrise manual Tipuri generate
Întreținere Manual pentru fiecare schimbare de schemă O singură comandă după fiecare schimbare
Derivă de schemă Posibilă și silențioasă Prinsă la generare sau la compilare
Boilerplate Crește cu fiecare operație Nimic de scris manual
Control asupra tipurilor custom Total Limitat la opțiunile de configurare ale pluginului
Dezvoltare condusă de schemă Verificare manuală încrucișată Compilatorul arată fiecare linie afectată

Codegen comparat cu tipurile scrise manual

GraphQL Codegen vs. introspecția GraphQL

Cele două sunt confundate pentru că Codegen folosește deseori introspecția. Totuși, rezolvă probleme diferite.

Introspecția este o funcționalitate din specificația GraphQL. Orice server GraphQL poate răspunde la interogări despre propria schemă prin câmpuri speciale precum __schema și __type. Rezultatul este o descriere JSON a fiecărui tip, câmp și argument expus de API.

Pe românește, introspecția răspunde la întrebarea „Cum arată acest API?”

Codegen răspunde la întrebarea „De ce cod are nevoie aplicația mea?”. Ia informațiile despre schemă și operațiile tale și scrie tipuri TypeScript și documente tipizate.

Când setezi schema la un URL în codegen.ts, Codegen trimite o interogare de introspecție către acel server pentru a obține schema. Cu un fișier local .graphql, introspecția nu are loc deloc.

Codegen poate produce și un rezultat de introspecție ca output. Pluginul @graphql-codegen/introspection scrie schema ca JSON de introspecție, pe care unele unelte, precum Graphcache al urql, îl folosesc pentru caching conștient de schemă.

  Introspecție GraphQL Codegen
Ce este Sistem de interogare integrat în GraphQL Instrument de linie de comandă și plugin
Input O interogare către un server în execuție O schemă și operațiile tale
Output Descriere JSON a schemei Tipuri, documente, cod de client
Când rulează Ori de câte ori un client întreabă serverul În timpul dezvoltării, build-urilor și CI

Codegen comparat cu introspecția

Probleme comune cu GraphQL Codegen

Majoritatea erorilor Codegen se reduc la una din patru probleme.

Lipsesc tipuri generate

Codegen rulează fără erori, dar tipul pe care îl aștepți nu e în fișierul de output.

Verifică aceste trei lucruri în ordine:

  • Schema: Dacă tipul nu există în schema pe care o încarcă Codegen, nu poate fi generat, așa că deschide fișierul de schemă sau rulează tu însuți interogarea de introspecție și confirmă că e acolo

  • Documente: Tipurile pentru operații există doar pentru operațiile pe care Codegen le-a găsit, deci dacă fișierul cu interogarea ta nu e potrivit de globul documents, tipurile lipsesc

  • Pluginuri: Din v6, typescript-operations generează doar enumurile și inputurile folosite de operațiile tale, așa că dacă ai nevoie de un tip complet de schemă precum Course, adaugă pluginul typescript într-un fișier de output separat

Dacă folosești un plugin pe care nu l-ai instalat, Codegen eșuează cu un mesaj clar:

Unable to find template plugin matching 'typescript-react-apollo'
Install one of the following packages:
- @graphql-codegen/typescript-react-apollo

Codegen nu găsește operațiile

Această eroare înseamnă că globul documents nu a potrivit niciun fișier cu o operație în el:

Unable to find any GraphQL type definitions for the following pointers:
  - src/**/*.gql

Verifică dacă extensia din glob se potrivește cu fișierele tale. Un pattern .gql nu va găsi fișiere .graphql, iar un pattern src/**/*.graphql nu va găsi operații inline în fișiere .ts.

Pentru operațiile inline, verifică și tag-ul. Codegen găsește doar template literals înveliți în gql sau marcați cu un comentariu /* GraphQL */. Un template string simplu cu o interogare înăuntru îi este invizibil.

Dacă setezi ignoreNoDocuments: true, Codegen nu afișează această eroare și generează un output fără tipuri pentru operații. Acea opțiune e utilă într-un proiect nou-nouț, dar poate ascunde un glob stricat mai târziu.

Tipurile generate sunt învechite

Tipurile se compilează, dar nu reflectă ultima schimbare a unei operații sau a schemei.

Codegen nu rulează singur. Rulează-l din nou după fiecare schimbare într-un fișier .graphql, o operație inline, schemă sau codegen.ts. În timpul dezvoltării, npm run codegen:watch se ocupă de asta pentru tine.

Dacă tipurile sunt învechite în CI, comite fișierele regenerate sau lasă hook-ul prebuild să le regenereze înainte de build.

Tipurile generate nu se potrivesc cu API-ul

Tipurile se compilează, dar datele de la server au altă formă la runtime.

Asta aproape întotdeauna înseamnă că Codegen și aplicația ta vorbesc cu scheme diferite. De exemplu, Codegen citește un schema.graphql local vechi de o săptămână, în timp ce aplicația apelează un API de producție care s-a schimbat între timp.

Verifică unde indică cheia schema. Dacă e un fișier, compară-l cu API-ul pe care îl apelează aplicația ta și actualizează-l. Dacă e un URL, asigură-te că este același mediu pe care îl folosește aplicația ta, nu un server de staging cu o schemă mai nouă sau mai veche.

Apoi regenerează și uită-te la diff. Dacă un fișier cu operații s-a schimbat, dar fișierul generat nu, probabil operația e în afara globului documents.

Bune practici pentru GraphQL Codegen

Codegen nu are nevoie de multă configurare ca să funcționeze bine. Aceste practici îl mențin așa pe măsură ce proiectul crește:

  • Ține schema și fișierele generate în sincron: Rulează modul watch în timpul dezvoltării și prebuild înainte de build-uri, ca nimeni să nu lucreze cu tipuri dintr-o schemă mai veche

  • Generează tipuri specifice operațiilor: Folosește typescript-operations pentru codul de client pentru că se potrivesc exact cu ce aduce fiecare interogare

  • Comite fișierele generate doar când fluxul tău de lucru are nevoie de ele: Fișierele comise îți permit să revezi schimbările de tip în pull request-uri și să rulezi --check în CI, iar dacă echipa ta nu are nevoie de asta, adaugă folderul de output în .gitignore și generează la fiecare build

  • Păstrează configurația scurtă: Un fișier de output cu două pluginuri acoperă majoritatea proiectelor client, iar fiecare opțiune în plus e ceva ce următorul developer trebuie să înțeleagă înainte de a schimba ceva

  • Rulează Codegen în CI la fiecare push: Un pas --check sau un hook prebuild prinde schimbările pe care cineva a uitat să le regenereze local

  • Nu edita niciodată fișierele generate de mână: Următoarea rulare Codegen îți suprascrie modificările, așa că rezolvă în schimb schema, operația sau configurația

Concluzie

GraphQL Codegen preia cea mai repetitivă parte a dezvoltării GraphQL — transformarea schemelor și operațiilor în tipuri TypeScript de mână.

Fluxul de lucru rămâne același indiferent cât de mare devine proiectul. Definiști schema și operațiile, configurezi Codegen, generezi tipurile și documentele tipizate și le imporți în codul aplicației tale.

Cea mai mare valoare este că tipurile tale descriu mereu API-ul pe care îl apelezi de fapt, nu codul pe care nu mai trebuie să-l scrii. Când se schimbă schema, compilatorul îți va spune dacă s-a stricat ceva, ca să poți prinde erorile devreme.

Dacă vrei să înveți cum este folosit GraphQL într-o bază de date NoSQL modernă, citește MongoDB și GraphQL — articolul nostru de blog.

Întrebări frecvente despre GraphQL Codegen

La ce este folosit GraphQL Codegen?

GraphQL Codegen generează cod dintr-o schemă GraphQL și din operațiile tale GraphQL. Majoritatea echipelor îl folosesc pentru a obține tipuri TypeScript și documente tipizate pentru interogări și mutații, ca să nu fie nevoie să scrie și să actualizeze acele tipuri de mână. Poate genera și tipuri pentru resolvere pe server și hook-uri gata făcute pentru clienți specifici.

Am nevoie de un server GraphQL în execuție ca să folosesc Codegen?

Nu. Codegen poate citi schema dintr-un fișier local .graphql, deci funcționează fără niciun server. Ai nevoie de un server în execuție doar dacă indici cheia schema către un URL, pentru că atunci Codegen aduce schema prin introspecție.

Ar trebui să comit fișierele generate în Git?

Depinde de fluxul de lucru al echipei tale. Fișierele comise îți permit să revezi schimbările de tip în pull request-uri și să folosești graphql-codegen --check în CI pentru a prinde output învechit. Dacă nu ai nevoie de asta, adaugă folderul de output în .gitignore și regenerează fișierele înainte de fiecare build.

Ar trebui să folosesc presetul client sau pluginuri individuale?

The Guild recomandă presetul client pentru majoritatea aplicațiilor client, deoarece împachetează tipurile pentru operații, documentele tipizate și fragment masking într-o singură setare. Apollo recomandă în schimb pluginul typescript-operations, deoarece presetul adaugă cod de runtime și funcționalități care intră în conflict cu Apollo Client. Dacă folosești Apollo, mergi pe pluginuri. Altfel, presetul este un implicit bun.

De ce primesc erori de declarații duplicate după ce actualizez Codegen?

Din v6, pluginul typescript-operations generează propriile tipuri pentru enumuri și inputuri. Dacă configurația ta listează în continuare atât typescript, cât și typescript-operations pentru același fișier de output, ambele pluginuri declară tipuri precum Level, iar TypeScript aruncă erori de declarații duplicate. Elimină pluginul typescript din acel output sau mută-l într-un fișier separat dacă încă ai nevoie de tipurile complete ale schemei.

Subiecte

Învață cu DataCamp

{count, plural, one {Curs} few {Cursuri} other {Cursuri}}

Software Development with Claude Code

{hours} ore
8.9K
Claude Code brings AI assistance to your terminal. Learn the workflows that turn it into a reliable tool for real software development.
Vezi detaliiRight Arrow
Începe Cursul

{count, plural, one {Traseu de învățare} few {Traseuri de învățare} other {Traseuri de învățare}}

Python pentru inginerie software

{hours} ore
Dezvoltă-ți abilitățile Python pentru ingineria software. Scrie funcții curate, stăpânește OOP, structurile de date și algoritmii și livrează cod bine testat.

{count, plural, one {Traseu de învățare} few {Traseuri de învățare} other {Traseuri de învățare}}

AI pentru ingineria software

{hours} ore
Scrie cod și construiește aplicații software mai rapid ca niciodată cu cele mai noi instrumente AI pentru dezvoltatori, inclusiv GitHub Copilot, Windsurf și Replit.
Afișează mai multRight Arrow