Introduction
GraphQL, créé par Facebook en 2012 et open-sourcé en 2015, révolutionne les APIs en permettant aux clients de demander exactement les données nécessaires, évitant le sur-fetching et under-fetching des REST traditionnelles. En 2026, avec Apollo Server v4 standalone, implémenter une API GraphQL est plus simple et performant que jamais, intégrant nativement TypeScript pour une typage fort.
Ce tutoriel intermédiaire vous guide pas à pas pour créer une API gérant des livres et auteurs : queries pour lire, mutations pour créer/mettre à jour, et resolvers avec relations. Imaginez une bibliothèque numérique où un client mobile requête un livre avec son auteur en une seule requête – c'est l'efficacité de GraphQL.
Pourquoi c'est crucial ? Les APIs modernes scalent mieux, réduisent la bande passante de 30-50% et accélèrent le développement frontend/backend. À la fin, vous aurez un serveur fonctionnel, testable en playground, prêt pour production avec auth et caching. (142 mots)
Prérequis
- Node.js 20+ installé
- Connaissances de base en TypeScript et Node.js
- Éditeur comme VS Code avec extension GraphQL
- npm ou yarn pour les dépendances
- 15 minutes pour tester localement
Initialiser le projet et installer les dépendances
mkdir graphql-api && cd graphql-api
npm init -y
npm install @apollo/server graphql typescript @types/node ts-node
npm install -D @types/graphql
mkdir srcCette commande crée un projet Node.js vide, installe Apollo Server v4 (standalone), GraphQL et TypeScript. ts-node permet d'exécuter TS directement. Évitez yarn si vous débutez pour coller aux standards npm 2026.
Définir le schéma GraphQL de base
Le schéma est le contrat de votre API : types, queries et mutations. Comme un blueprint d'architecture, il décrit les données sans implémentation. Nous commençons par Book et Author avec une relation one-to-many.
Créer le fichier schema.graphql
type Book {
id: ID!
title: String!
authorId: ID!
published: Int!
}
type Author {
id: ID!
name: String!
books: [Book!]!
}
type Query {
books: [Book!]!
book(id: ID!): Book
authors: [Author!]!
author(id: ID!): Author
}
type Mutation {
createBook(title: String!, authorId: ID!, published: Int!): Book!
}Ce schéma définit deux types avec champs obligatoires (!). Les queries listent/ récupèrent, la mutation crée un livre. La relation books sur Author sera résolue côté resolvers. Piège : oubliez les ! pour les non-nullables et les clients crashent.
Implémenter les resolvers pour queries et données mock
import { IResolvers } from '@graphql-tools/utils';
import { makeExecutableSchema } from '@graphql-tools/schema';
type Book = { id: string; title: string; authorId: string; published: number };
type Author = { id: string; name: string; books: Book[] };
const books: Book[] = [
{ id: '1', title: 'Le Petit Prince', authorId: '1', published: 1943 },
{ id: '2', title: '1984', authorId: '2', published: 1949 }
];
const authors: Author[] = [
{ id: '1', name: 'Saint-Exupéry', books: [] },
{ id: '2', name: 'George Orwell', books: [] }
];
authors[0].books = [books[0]];
authors[1].books = [books[1]];
const resolvers: IResolvers = {
Query: {
books: () => books,
book: (_parent, { id }) => books.find(b => b.id === id) || null,
authors: () => authors,
author: (_parent, { id }) => authors.find(a => a.id === id) || null,
},
Author: {
books: (parent: Author) => authors.find(a => a.id === parent.id)?.books || [],
}
};
export { resolvers, makeExecutableSchema };Les resolvers mappent schema à données (ici mock in-memory). Query.books retourne la liste ; Author.books résout la relation. Utilisez @graphql-tools pour schema exécutable. Piège : sans resolver pour relations, GraphQL renvoie null et casse les queries nested.
Lancer le serveur Apollo Server
Apollo Server v4 est standalone : pas besoin d'Express. Il expose un playground à http://localhost:4000 pour tester queries live.
Créer le serveur principal
import { ApolloServer } from '@apollo/server/standalone';
import { readFileSync } from 'fs';
import { resolvers } from './resolvers.js';
import { makeExecutableSchema } from '@graphql-tools/schema.js';
const typeDefs = readFileSync('./src/schema.graphql', { encoding: 'utf-8' });
const schema = makeExecutableSchema({ typeDefs, resolvers });
const server = new ApolloServer({
schema,
});
(async () => {
const { url } = await server.listen({ port: 4000 });
console.log(`🚀 Server ready at ${url}`);
})();Lit le schema, lie resolvers, lance sur port 4000. makeExecutableSchema fusionne tout. Exécutez avec npx ts-node src/server.ts. Piège : oubliez .js sur imports TS (ESM 2026) ou le serveur ne démarre pas.
Ajouter une mutation createBook fonctionnelle
import { IResolvers } from '@graphql-tools/utils';
import { makeExecutableSchema } from '@graphql-tools/schema';
type Book = { id: string; title: string; authorId: string; published: number };
type Author = { id: string; name: string; books: Book[] };
let books: Book[] = [
{ id: '1', title: 'Le Petit Prince', authorId: '1', published: 1943 },
{ id: '2', title: '1984', authorId: '2', published: 1949 }
];
let authors: Author[] = [
{ id: '1', name: 'Saint-Exupéry', books: [] },
{ id: '2', name: 'George Orwell', books: [] }
];
authors[0].books = [books[0]];
authors[1].books = [books[1]];
const nextId = () => (parseInt(books[books.length - 1]?.id || '0') + 1).toString();
const resolvers: IResolvers = {
Query: {
books: () => books,
book: (_parent, { id }) => books.find(b => b.id === id) || null,
authors: () => authors,
author: (_parent, { id }) => authors.find(a => a.id === id) || null,
},
Mutation: {
createBook: (_parent, { title, authorId, published }, _context) => {
const newBook: Book = { id: nextId(), title, authorId, published };
books.push(newBook);
const author = authors.find(a => a.id === authorId);
if (author) author.books.push(newBook);
return newBook;
},
},
Author: {
books: (parent: Author) => authors.find(a => a.id === parent.id)?.books || [],
}
};
export { resolvers, makeExecutableSchema };Ajoute Mutation.createBook : génère ID unique, push dans arrays, update relation. Données mutables avec let. Testez en playground : mutation { createBook(...) { id title } }. Piège : sans update bidirectionnel (author.books), les relations se cassent.
Gérer le contexte pour l'authentification
À niveau intermédiaire, ajoutez un contexte pour passer user/auth à tous resolvers, comme un tunnel de données global par requête.
Ajouter contexte et middleware auth
import { ApolloServer } from '@apollo/server/standalone';
import { readFileSync } from 'fs';
import { resolvers } from './resolvers.js';
import { makeExecutableSchema } from '@graphql-tools/schema.js';
import type { ContextValue } from './context.js';
const typeDefs = readFileSync('./src/schema.graphql', { encoding: 'utf-8' });
const schema = makeExecutableSchema({ typeDefs, resolvers });
const server = new ApolloServer({
schema,
introspection: true,
});
(async () => {
const { url } = await server.listen({
port: 4000,
context: async ({ req }) => ({
user: req.headers.authorization === 'Bearer token' ? { id: 'user1' } : null,
} as ContextValue),
});
console.log(`🚀 Server ready at ${url}`);
})();Context extrait token d'header, passe user à resolvers. Utilisez dans resolvers via args.context. Introspection activée pour playground. Piège : context async sinon headers manquants en prod.
TypeScript pour contexte (fichier context.ts)
export interface ContextValue {
user: { id: string } | null;
}
export type ResolverContext = {
contextValue: ContextValue;
};Types stricts pour context, évite any. Importez dans resolvers pour autocomplétion. Essentiel en TS pour scaler sans bugs runtime.
Bonnes pratiques
- Validez inputs avec class-validator dans resolvers pour sécurité.
- Cachez queries avec Apollo's built-in caching ou Redis.
- Paginez listes : ajoutez first/after à queries pour N+1 avoidance.
- Utilisez DataLoader pour batcher relations et éviter N+1 queries.
- Générez types TS auto avec graphql-codegen pour frontend.
Erreurs courantes à éviter
- N+1 problem : résolvez relations sans DataLoader, surcharge DB.
- Oublier non-nullable : champ ! sans fallback = erreur GraphQL fatale.
- Pas de context : auth stateless impossible, sécurité compromise.
- Schema statique : sans codegen, types drift entre BE/FE.
Pour aller plus loin
Maîtrisez subscriptions avec WebSockets via Apollo. Intégrez Prisma pour DB réelle. Découvrez nos formations Learni sur GraphQL et Apollo pour pro-level : federation, stitching et perf tuning. Docs officielles : Apollo GraphQL.