Skip to content
Learni
Voir tous les tutoriels
Backend

Comment implémenter GraphQL avancé avec Apollo en 2026

Read in English

Introduction

GraphQL est le standard pour les APIs modernes en 2026, surpassant REST par sa flexibilité et son efficacité. Ce tutoriel avancé vous guide dans l'implémentation d'une API complète avec Apollo Server 4, Prisma 6 pour l'ORM, authentification JWT, batching de requêtes, caching et subscriptions WebSocket pour du real-time. Imaginez une app de blog avec users, posts et commentaires : clients demandent exactement les données nécessaires, sans sur-fetching.

Pourquoi c'est crucial ? Les APIs GraphQL réduisent la latence de 40-60% en production, gèrent la complexité N+1 via DataLoader, et scalent avec federation. Nous construirons un serveur TypeScript production-ready, testable et sécurisé. À la fin, vous déployez sur Vercel ou un cluster Kubernetes. Prêt à booster vos backends ? (128 mots)

Prérequis

  • Node.js 20+ et npm 10+
  • Connaissances avancées en TypeScript et async/await
  • Familiarité avec Prisma et bases de données relationnelles
  • Outils : GraphQL Playground ou Apollo Studio pour tester

Initialisation du projet

terminal
mkdir graphql-advanced-api && cd graphql-advanced-api
npm init -y
npm install @apollo/server @apollo/server-plugin-drain-http-server graphql prisma @prisma/client jsonwebtoken bcryptjs @types/jsonwebtoken @types/bcryptjs @types/node
databases/sqlite
docker run --name prisma-db -e POSTGRES_PASSWORD=prisma -p 5432:5432 -d postgres
npm install -D typescript ts-node @types/express express cors
npx prisma init --datasource-provider sqlite

Ce script initialise un projet Node.js avec Apollo Server, Prisma (SQLite pour simplicité, portable), JWT pour auth et dépendances TypeScript. On lance un conteneur Postgres optionnel ; ici SQLite pour standalone. Évitez les DB cloud en dev pour la reproductibilité.

Configuration de Prisma

Prisma génère un client type-safe pour notre schéma : Users, Posts, Comments avec relations. On définit les modèles avancés avec indexes et uniques pour scaler.

Schéma Prisma complet

prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

model User {
  id       String   @id @default(uuid())
  email    String   @unique
  password String
  name     String?
  posts    Post[]
  createdAt DateTime @default(now())
}

model Post {
  id          String     @id @default(uuid())
  title       String
  content     String
  authorId    String
  author      User       @relation(fields: [authorId], references: [id])
  comments    Comment[]
  createdAt   DateTime   @default(now())
  @@index([authorId])
}

model Comment {
  id        String   @id @default(uuid())
  content   String
  postId    String
  post      Post     @relation(fields: [postId], references: [id])
  createdAt DateTime @default(now())
  @@index([postId])
}

Schéma relationnel complet avec UUID, indexes pour perf et timestamps. SQLite pour dev rapide ; migrez vers Postgres en prod. Exécutez npx prisma migrate dev --name init puis npx prisma generate après.

TypeDefs GraphQL avancés

src/schema.ts
import { gql } from 'graphql-tag';

export const typeDefs = gql`
  type User {
    id: ID!
    email: String!
    name: String
    posts: [Post!]!
  }

  type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
    comments: [Comment!]!
  }

  type Comment {
    id: ID!
    content: String!
    post: Post!
  }

  type Query {
    me: User
    posts: [Post!]!
    post(id: ID!): Post
  }

  type Mutation {
    createPost(title: String!, content: String!): Post!
    deletePost(id: ID!): Boolean!
    addComment(postId: ID!, content: String!): Comment!
  }

  type Subscription {
    postAdded: Post!
  }

  scalar DateTime
`; 

TypeDefs stricts avec relations scalaires et subscriptions. Pas de N+1 grâce aux resolvers. Ajoutez DateTime scalar pour timestamps Prisma.

Resolvers avec DataLoader et auth

Les resolvers gèrent la logique métier : batching anti-N+1 via DataLoader, auth JWT dans context, hashing bcrypt.

Utils JWT et bcrypt

src/utils/auth.ts
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';

const JWT_SECRET = process.env.JWT_SECRET || 'supersecretdevkey2026';

export const hashPassword = async (password: string): Promise<string> => {
  return bcrypt.hash(password, 12);
};

export const verifyPassword = async (password: string, hash: string): Promise<boolean> => {
  return bcrypt.compare(password, hash);
};

export const createToken = (payload: { userId: string }): string => {
  return jwt.sign(payload, JWT_SECRET, { expiresIn: '1h' });
};

export const verifyToken = (token: string): { userId: string } | null => {
  try {
    return jwt.verify(token, JWT_SECRET) as { userId: string };
  } catch {
    return null;
  }
};

Fonctions utilitaires sécurisées pour JWT (expiresIn court) et bcrypt (salt 12). Env JWT_SECRET en prod via dotenv. Piège : ne jamais commiter les secrets.

Context avec auth et DataLoader

src/context.ts
import { Request } from 'express';
import DataLoader from 'dataloader';
import { PrismaClient } from '@prisma/client';
import { verifyToken } from './utils/auth';

const prisma = new PrismaClient();

export interface Context {
  prisma: PrismaClient;
  userId: string | null;
  loaders: {
    users: DataLoader<string, any>;
    postsByUser: DataLoader<string, any[]>;
  };
}

export const createContext = ({ req }: { req: Request }): Context => {
  const token = req.headers.authorization?.replace('Bearer ', '') || '';
  const userId = verifyToken(token)?.userId || null;
  return {
    prisma,
    userId,
    loaders: {
      users: new DataLoader(async (ids) => {/* impl batch */}),
      postsByUser: new DataLoader(async (userIds) => {/* batch posts */}),
    },
  };
};

Context avancé injecte Prisma, user auth et DataLoaders pour batching. Implémentez les batch functions dans resolvers. Évite les pièges de mémoire : disposez Prisma en shutdown.

Resolvers complets avec batching

src/resolvers.ts
import { Resolvers } from './types'; // généré par codegen
import { Context } from './context';

export const resolvers: Resolvers<Context> = {
  Query: {
    me: async (_, __, { userId, prisma }) => {
      if (!userId) throw new Error('Non authentifié');
      return prisma.user.findUnique({ where: { id: userId }, include: { posts: true } });
    },
    posts: async (_, __, { prisma }) => {
      return prisma.post.findMany({ include: { author: true, comments: true } });
    },
    post: async (_, { id }, { prisma }) => {
      return prisma.post.findUnique({ where: { id }, include: { author: true, comments: true } });
    },
  },
  Post: {
    author: async (parent, _, { loaders }) => {
      return loaders.users.load(parent.authorId);
    },
  },
  Mutation: {
    createPost: async (_, { title, content }, { userId, prisma }) => {
      if (!userId) throw new Error('Auth required');
      return prisma.post.create({ data: { title, content, authorId: userId }, include: { author: true } });
    },
  },
  Subscription: {
    postAdded: {
      subscribe: () => {/* pubsub impl */},
    },
  },
};

Resolvers type-safe avec guards auth, includes Prisma pour relations, DataLoader hooks. Ajoutez PubSub pour subs. Piège N+1 évité : batch via loaders.

Serveur Apollo avec plugins

src/server.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { expressMiddleware } from '@apollo/server/express4';
import express from 'express';
import cors from 'cors';
import { typeDefs } from './schema';
import { resolvers } from './resolvers';
import { createContext } from './context';

const app = express();
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

await server.start();

app.use('/graphql', cors<cors.CorsRequest>(), express.json(), expressMiddleware(server, { context: createContext }));

const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
console.log(`🚀 Server ready at ${url}`);

Serveur hybride Express/Standalone avec middleware context. Plugins CORS/JSON auto. Cache par défaut activé. Lancez avec ts-node src/server.ts et testez sur http://localhost:4000/graphql.

Test et subscriptions

Query exemple : { posts { title author { name } } }. Mutation : mutation { createPost(title: "Test", content: "Contenu") { id } } avec header Authorization: Bearer . Subscriptions via WS : subscription { postAdded { title } }.

Client Apollo pour tester

test-client.js
const { ApolloClient, InMemoryCache, gql, split, HttpLink, WebSocketLink } = require('@apollo/client');
const { getMainDefinition } = require('@apollo/client/utilities');

const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });
const wsLink = new WebSocketLink({ uri: 'ws://localhost:4000/graphql', options: { reconnect: true } });

const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
  },
  wsLink,
  httpLink
);

const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache() });

client.query({ query: gql`query { posts { title } }` }).then(console.log);

Client Apollo split HTTP/WS pour queries/mutations vs subs. Cache InMemory. Installez @apollo/client et exécutez pour valider. Piège : WS uri sans 'http'.

Bonnes pratiques

  • Batching obligatoire : Toujours DataLoader pour relations 1:N.
  • Auth par field : Guards dans resolvers, pas global.
  • Caching avancé : Apollo Cache + Redis en prod.
  • Rate limiting : Apollo Server plugin ou middleware Express.
  • Schema stitching : Pour microservices federation.
  • Monitoring : Apollo Studio ou Prometheus metrics.

Erreurs courantes à éviter

  • N+1 queries : Sans DataLoader, 100 posts = 100+ DB calls.
  • JWT sans verify : Token invalides crashent sans try/catch.
  • Subscriptions sans PubSub : new PubSub() from 'graphql-subscriptions'.
  • Prisma disconnect : Ajoutez prisma.$disconnect() on shutdown.
  • Scalar manquants : Définissez DateTime avec custom scalar.

Pour aller plus loin

Approfondissez avec Apollo Federation pour microservices. Migrez vers Postgres/Kafka pour scale. Découvrez nos formations Learni sur GraphQL avancé et le workshop Prisma + Apollo.