apollo-server

Guide pour créer des serveurs GraphQL avec Apollo Server 5.x. Utilisez cette skill dans les cas suivants : (1) mise en place d'un nouveau projet Apollo Server, (2) écriture de resolvers ou définition de schémas GraphQL, (3) implémentation de l'authentification ou de l'autorisation, (4) création de plugins ou de sources de données personnalisées, (5) résolution d'erreurs ou de problèmes de performance liés à Apollo Server.

npx skills add https://github.com/apollographql/skills --skill apollo-server

Guide Apollo Server 5.x

Apollo Server est un serveur GraphQL open-source qui fonctionne avec tout schéma GraphQL. Apollo Server 5 est agnostique au framework et s'exécute de manière autonome ou s'intègre avec Express, Fastify et les environnements serverless.

Démarrage rapide

Étape 1 : Installation

npm install @apollo/server graphql

Pour l'intégration Express :

npm install @apollo/server @as-integrations/express5 express graphql cors

Étape 2 : Définir le schéma

const typeDefs = `#graphql
  type Book {
    title: String
    author: String
  }

  type Query {
    books: [Book]
  }
`;

Étape 3 : Écrire les resolvers

const resolvers = {
  Query: {
    books: () => [
      { title: "The Great Gatsby", author: "F. Scott Fitzgerald" },
      { title: "1984", author: "George Orwell" },
    ],
  },
};

Étape 4 : Démarrer le serveur

Mode autonome (Recommandé pour le prototypage) :

Le serveur autonome est parfait pour le prototypage, mais pour les services en production, nous recommandons d'intégrer Apollo Server avec un framework web plus complet comme Express, Koa ou Fastify. Passer du serveur autonome à un framework web ultérieurement est simple.

import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";

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

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`Server ready at ${url}`);

Express :

import { ApolloServer } from "@apollo/server";
import { expressMiddleware } from "@as-integrations/express5";
import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHttpServer";
import express from "express";
import http from "http";
import cors from "cors";

const app = express();
const httpServer = http.createServer(app);

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});

await server.start();

app.use(
  "/graphql",
  cors(),
  express.json(),
  expressMiddleware(server, {
    context: async ({ req }) => ({ token: req.headers.authorization }),
  }),
);

await new Promise<void>((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log("Server ready at http://localhost:4000/graphql");

Définition du schéma

Types scalaires

  • Int - Entier 32 bits
  • Float - Nombre décimal double précision
  • String - Chaîne UTF-8
  • Boolean - vrai/faux
  • ID - Identifiant unique (sérialisé en String)

Définitions de types

type User {
  id: ID!
  name: String!
  email: String
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String
  author: User!
}

input CreatePostInput {
  title: String!
  content: String
}

type Query {
  user(id: ID!): User
  users: [User!]!
}

type Mutation {
  createPost(input: CreatePostInput!): Post!
}

Enums et interfaces

enum Status {
  DRAFT
  PUBLISHED
  ARCHIVED
}

interface Node {
  id: ID!
}

type Article implements Node {
  id: ID!
  title: String!
}

Vue d'ensemble des resolvers

Les resolvers suivent la signature : (parent, args, contextValue, info)

  • parent : Résultat du resolver parent (les resolvers root reçoivent undefined)
  • args : Arguments passés au champ
  • contextValue : Objet de contexte partagé (authentification, dataSources, etc.)
  • info : Informations spécifiques au champ et détails du schéma (rarement utilisé)
const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources }) => {
      return dataSources.usersAPI.getUser(id);
    },
  },
  User: {
    posts: async (parent, _, { dataSources }) => {
      return dataSources.postsAPI.getPostsByAuthor(parent.id);
    },
  },
  Mutation: {
    createPost: async (_, { input }, { dataSources, user }) => {
      if (!user) throw new GraphQLError("Not authenticated");
      return dataSources.postsAPI.create({ ...input, authorId: user.id });
    },
  },
};

Configuration du contexte

Le contexte est créé par requête et passé à tous les resolvers.

interface MyContext {
  token?: string;
  user?: User;
  dataSources: {
    usersAPI: UsersDataSource;
    postsAPI: PostsDataSource;
  };
}

const server = new ApolloServer<MyContext>({
  typeDefs,
  resolvers,
});

// Mode autonome
const { url } = await startStandaloneServer(server, {
  context: async ({ req }) => ({
    token: req.headers.authorization || "",
    user: await getUser(req.headers.authorization || ""),
    dataSources: {
      usersAPI: new UsersDataSource(),
      postsAPI: new PostsDataSource(),
    },
  }),
});

// Middleware Express
expressMiddleware(server, {
  context: async ({ req, res }) => ({
    token: req.headers.authorization,
    user: await getUser(req.headers.authorization),
    dataSources: {
      usersAPI: new UsersDataSource(),
      postsAPI: new PostsDataSource(),
    },
  }),
});

Fichiers de référence

Documentation détaillée pour des sujets spécifiques :

Règles clés

Conception du schéma

  • Utilisez ! (non-null) pour les champs qui ont toujours des valeurs
  • Préférez les types input pour les mutations plutôt que les arguments inline
  • Utilisez des interfaces pour les types polymorphes
  • Conservez les descriptions du schéma pour la documentation

Bonnes pratiques des resolvers

  • Gardez les resolvers simples - déléguez aux services/sources de données
  • Gérez toujours les erreurs explicitement
  • Utilisez DataLoader pour regrouper les requêtes connexes
  • Retournez des données partielles quand c'est possible (la force de GraphQL)

Performance

  • Utilisez @defer et @stream pour les grandes réponses
  • Implémentez DataLoader pour résoudre les requêtes N+1
  • Envisagez les requêtes persistantes pour la production
  • Utilisez les en-têtes de cache et CDN le cas échéant

Règles fondamentales

  • UTILISEZ TOUJOURS les motifs Apollo Server 5.x (pas v4 ou versions antérieures)
  • TYPEZ TOUJOURS votre contexte avec les génériques TypeScript
  • UTILISEZ TOUJOURS GraphQLError du package graphql pour les erreurs
  • N'EXPOSEZ JAMAIS les stack traces dans les erreurs de production
  • PRÉFÉREZ startStandaloneServer pour le prototypage uniquement
  • UTILISEZ une intégration avec un framework serveur comme Express, Koa, Fastify, Next, etc. pour les applications en production
  • IMPLÉMENTEZ l'authentification dans le contexte, l'autorisation dans les resolvers

Skills similaires