ToolBox Hub
Vue detaillee de code de programmation dans un theme sombre sur un ecran d'ordinateur.

REST API vs GraphQL vs tRPC en 2026 : Lequel devriez-vous utiliser ?

đź“· Stanislav Kondratiev / Pexels

REST API vs GraphQL vs tRPC en 2026 : Lequel devriez-vous utiliser ?

Une comparaison detaillee de REST, GraphQL et tRPC pour le developpement web moderne. Decouvrez les avantages, inconvenients, caracteristiques de performance et meilleurs cas d'utilisation de chaque approche.

19 mars 202614 min de lecture

Choisir comment votre frontend communique avec votre backend est l'une des decisions architecturales les plus consequentes dans un projet web. En 2026, les developpeurs disposent de trois options matures et eprouvees : REST, GraphQL et tRPC. Chacune possede des forces distinctes, des compromis et des cas d'utilisation ideaux. Cet article fournit une comparaison approfondie pour vous aider a faire le bon choix pour votre prochain projet.

REST API : Le standard etabli

REST (Representational State Transfer) est le paradigme d'API dominant depuis plus de deux decennies. Il fait correspondre les methodes HTTP aux operations CRUD sur les ressources, le rendant intuitif et compris par pratiquement tous les developpeurs.

Comment fonctionne REST

Les REST APIs exposent des ressources sous forme d'URLs. Les clients interagissent avec elles en utilisant les methodes HTTP standard :

// GET une liste d'utilisateurs
const response = await fetch('https://api.example.com/users');
const users = await response.json();

// GET un seul utilisateur
const user = await fetch('https://api.example.com/users/42');

// POST pour creer un nouvel utilisateur
const newUser = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Alice Johnson',
    email: 'alice@example.com',
  }),
});

// PUT pour mettre a jour un utilisateur
await fetch('https://api.example.com/users/42', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Alice Smith' }),
});

// DELETE un utilisateur
await fetch('https://api.example.com/users/42', {
  method: 'DELETE',
});

Exemple REST cote serveur (Express)

import express from 'express';

const app = express();
app.use(express.json());

// GET /users
app.get('/users', async (req, res) => {
  const { page = 1, limit = 20, sort = 'name' } = req.query;
  const users = await db.users.findMany({
    skip: (Number(page) - 1) * Number(limit),
    take: Number(limit),
    orderBy: { [sort as string]: 'asc' },
  });
  res.json({ data: users, page: Number(page), limit: Number(limit) });
});

// GET /users/:id
app.get('/users/:id', async (req, res) => {
  const user = await db.users.findUnique({
    where: { id: Number(req.params.id) },
    include: { posts: true, profile: true },
  });
  if (!user) return res.status(404).json({ error: 'User not found' });
  res.json(user);
});

// POST /users
app.post('/users', async (req, res) => {
  const user = await db.users.create({ data: req.body });
  res.status(201).json(user);
});

Forces de REST

  • Universellement compris : Chaque developpeur, chaque langage, chaque plateforme supporte REST.
  • Mise en cache HTTP : Les requetes GET peuvent etre mises en cache par les navigateurs, CDNs et proxies nativement.
  • Sans etat et simple : Chaque requete contient toutes les informations dont le serveur a besoin.
  • Outillage mature : Swagger/OpenAPI, Postman et d'innombrables bibliotheques.
  • Les codes de statut sont significatifs : 200, 201, 404, 422, 500 transmettent chacun des informations specifiques.

Faiblesses de REST

  • Over-fetching : Un endpoint retourne tous les champs, meme si le client n'en a besoin que de quelques-uns.
  • Under-fetching : Obtenir des donnees liees necessite souvent plusieurs allers-retours.
  • Pas de surete de types integree : Sans outillage supplementaire comme la generation de code OpenAPI, il n'y a pas de contrat entre client et serveur.
  • Defis de versionnement : Les changements incompatibles necessitent souvent des endpoints /v2/ ou un versionnement base sur les en-tetes.

GraphQL : Requetes flexibles

GraphQL, cree par Facebook et publie en open source en 2015, permet au client de specifier exactement les donnees dont il a besoin en une seule requete. Il utilise un schema type pour definir la surface de l'API.

Comment fonctionne GraphQL

// Une requete GraphQL
const query = `
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
      posts(first: 5) {
        title
        createdAt
      }
      followers {
        totalCount
      }
    }
  }
`;

const response = await fetch('https://api.example.com/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    query,
    variables: { id: '42' },
  }),
});

const { data } = await response.json();
// data.user.name, data.user.posts, data.user.followers.totalCount

Schema GraphQL cote serveur

// schema.ts (en utilisant une approche schema-first)
const typeDefs = `
  type User {
    id: ID!
    name: String!
    email: String!
    posts(first: Int, after: String): PostConnection!
    followers: FollowerCount!
  }

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

  type PostConnection {
    edges: [PostEdge!]!
    pageInfo: PageInfo!
  }

  type Query {
    user(id: ID!): User
    users(first: Int, after: String): UserConnection!
    post(id: ID!): Post
  }

  type Mutation {
    createUser(input: CreateUserInput!): User!
    updateUser(id: ID!, input: UpdateUserInput!): User!
    deleteUser(id: ID!): Boolean!
  }

  type Subscription {
    postCreated: Post!
  }
`;

Resolvers

const resolvers = {
  Query: {
    user: async (_: unknown, { id }: { id: string }, ctx: Context) => {
      return ctx.db.users.findUnique({ where: { id } });
    },
    users: async (_: unknown, args: PaginationArgs, ctx: Context) => {
      return paginateUsers(ctx.db, args);
    },
  },
  User: {
    posts: async (parent: User, args: PaginationArgs, ctx: Context) => {
      return ctx.loaders.userPosts.load({ userId: parent.id, ...args });
    },
    followers: async (parent: User, _: unknown, ctx: Context) => {
      const count = await ctx.db.followers.count({
        where: { followeeId: parent.id },
      });
      return { totalCount: count };
    },
  },
  Mutation: {
    createUser: async (_: unknown, { input }: { input: CreateUserInput }, ctx: Context) => {
      return ctx.db.users.create({ data: input });
    },
  },
};

Forces de GraphQL

  • Pas d'over-fetching ni d'under-fetching : Les clients demandent exactement les champs dont ils ont besoin.
  • Un seul endpoint : Toutes les operations passent par une URL.
  • Schema fortement type : Le schema sert de documentation vivante et permet un outillage puissant.
  • Introspection : Les clients peuvent decouvrir toute la surface de l'API de maniere programmatique.
  • Subscriptions : Support integre pour les donnees en temps reel via WebSockets.
  • Ideal pour des clients divers : Une application mobile peut demander des champs minimaux tandis qu'une application desktop recupere tout.

Faiblesses de GraphQL

  • Complexite : Les resolvers, data loaders, persisted queries et la gestion du schema ajoutent une charge cognitive.
  • La mise en cache est plus difficile : Puisque toutes les requetes sont des POSTs vers un seul endpoint, la mise en cache HTTP traditionnelle ne fonctionne pas. Vous avez besoin d'une mise en cache cote client (Apollo, urql) ou de persisted queries.
  • Probleme de requetes N+1 : Sans data loaders, les resolvers imbriques peuvent declencher des requetes excessives a la base de donnees.
  • Telechargement de fichiers : Non supporte nativement ; necessite des specifications de requetes multipart ou des endpoints de telechargement separes.
  • Surface de securite : Des requetes profondement imbriquees ou couteuses peuvent causer un deni de service sans limitation de profondeur de requete et analyse de complexite.

tRPC : Surete de types de bout en bout

tRPC est un framework specifique a TypeScript qui vous permet d'appeler des fonctions serveur directement depuis le client avec une surete de types complete et sans generation de code. Il est concu pour les projets monorepo ou le frontend et le backend sont tous deux ecrits en TypeScript.

Comment fonctionne tRPC

tRPC elimine la couche API en tant que concept separe. Vous definissez des procedures sur le serveur, et le client les appelle comme si c'etaient des fonctions locales.

Definition du routeur cote serveur :

// server/routers/user.ts
import { z } from 'zod';
import { router, publicProcedure, protectedProcedure } from '../trpc';

export const userRouter = router({
  getById: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input, ctx }) => {
      const user = await ctx.db.users.findUnique({
        where: { id: input.id },
        include: { posts: true, profile: true },
      });
      if (!user) throw new TRPCError({ code: 'NOT_FOUND' });
      return user;
    }),

  list: publicProcedure
    .input(z.object({
      page: z.number().default(1),
      limit: z.number().min(1).max(100).default(20),
      search: z.string().optional(),
    }))
    .query(async ({ input, ctx }) => {
      const { page, limit, search } = input;
      const where = search
        ? { name: { contains: search, mode: 'insensitive' as const } }
        : {};
      const [users, total] = await Promise.all([
        ctx.db.users.findMany({
          where,
          skip: (page - 1) * limit,
          take: limit,
        }),
        ctx.db.users.count({ where }),
      ]);
      return { users, total, page, limit };
    }),

  create: protectedProcedure
    .input(z.object({
      name: z.string().min(1).max(100),
      email: z.string().email(),
    }))
    .mutation(async ({ input, ctx }) => {
      return ctx.db.users.create({ data: input });
    }),

  update: protectedProcedure
    .input(z.object({
      id: z.string(),
      name: z.string().min(1).max(100).optional(),
      email: z.string().email().optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      const { id, ...data } = input;
      return ctx.db.users.update({ where: { id }, data });
    }),
});

Utilisation cote client (avec React) :

// Client : surete de types complete, sans generation de code necessaire
import { trpc } from '../utils/trpc';

function UserProfile({ userId }: { userId: string }) {
  const { data: user, isLoading, error } = trpc.user.getById.useQuery({ id: userId });

  const updateUser = trpc.user.update.useMutation({
    onSuccess: () => {
      // Invalider et recuperer a nouveau
      utils.user.getById.invalidate({ id: userId });
    },
  });

  if (isLoading) return <Skeleton />;
  if (error) return <ErrorMessage error={error.message} />;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <button onClick={() => updateUser.mutate({ id: userId, name: 'New Name' })}>
        Mettre a jour le nom
      </button>
    </div>
  );
}

Chaque entree, sortie et erreur est entierement typee. Si vous changez le nom d'un champ sur le serveur, le client affichera une erreur TypeScript instantanement.

Forces de tRPC

  • Surete de types de bout en bout : Les changements sur le serveur se refletent immediatement dans les types du client sans generation de code.
  • Boilerplate minimal : Pas de fichiers de schema, pas de definitions de routes API, pas d'etape de generation de client.
  • Excellente experience developpeur : Autocompletion, documentation en ligne et support du refactoring dans votre IDE.
  • Validation des entrees integree : Les schemas Zod valident les entrees a l'execution et fournissent les types a la compilation.
  • Integration React Query : Support de premiere classe pour la mise en cache, les mises a jour optimistes et le refetching en temps reel.
  • Subscriptions : Support WebSocket pour les fonctionnalites en temps reel.

Faiblesses de tRPC

  • TypeScript uniquement : Le client et le serveur doivent etre en TypeScript. Ne fonctionne pas avec Python, Go ou d'autres langages backend.
  • Centre sur le monorepo : Fonctionne mieux quand le client et le serveur partagent une base de code. Moins pratique quand les equipes travaillent dans des depots separes.
  • Pas de cas d'utilisation d'API publique : Non adapte aux APIs consommees par des tiers puisqu'il n'y a pas de schema independant du langage.
  • Client et serveur couples : Le couplage etroit peut devenir une preoccupation de maintenance a mesure que les equipes grandissent.
  • Ecosysteme plus petit : Moins de tutoriels, d'outils et de ressources communautaires par rapport a REST ou GraphQL.

Tableau comparatif

CaracteristiqueRESTGraphQLtRPC
Surete de typesManuelle (OpenAPI codegen)Basee sur le schemaAutomatique (TypeScript)
Over-fetchingCourantElimineMinimal (retours types)
Under-fetchingCourant (N+1 requetes)ElimineMinimal
Mise en cacheExcellente (HTTP natif)Complexe (cote client)React Query integre
Courbe d'apprentissageFaibleMoyenne-EleveeFaible (si vous connaissez TS)
Maturite de l'outillageExcellenteTres bonneBonne
Support de langagesTousTousTypeScript uniquement
Temps reelWebSockets/SSE (separe)SubscriptionsSubscriptions
Telechargement de fichiersNatifSolution de contournementSolution de contournement
APIs publiquesIdealBonNon adapte
Generation de codeOptionnelle (OpenAPI)Souvent necessaireNon necessaire
PerformanceBonneBonne (avec batching)Excellente (overhead minimal)
Documentation APISwagger/OpenAPIIntrospection du schemaTypes TypeScript

Quand utiliser chacun

Choisissez REST quand

  • Vous construisez une API publique consommee par des tiers dans differents langages.
  • Vous avez besoin de mise en cache HTTP au niveau CDN ou navigateur (par exemple, un site riche en contenu).
  • Votre equipe comprend des developpeurs plus familiers avec les APIs HTTP traditionnelles.
  • Vous construisez des microservices qui communiquent entre eux a travers differents runtimes de langages.
  • Vous souhaitez l'architecture la plus simple possible pour une application CRUD directe.

Choisissez GraphQL quand

  • Vous avez plusieurs clients (web, mobile, TV, montre) qui ont besoin de formes de donnees differentes de la meme API.
  • Vos donnees sont profondement relationnelles et les clients ont frequemment besoin de donnees imbriquees en une seule requete.
  • Vous avez besoin de fonctionnalites en temps reel (subscriptions) etroitement integrees a votre couche de requetes.
  • Vous voulez une API auto-documentee avec introspection pour l'outillage developpeur.
  • Vous construisez un gateway qui agrege des donnees de plusieurs services backend.

Choisissez tRPC quand

  • Votre stack est entierement en TypeScript (frontend et backend).
  • Vous travaillez dans un monorepo ou le code client et serveur cohabitent.
  • Vous valorisez la velocite de developpement et souhaitez la vitesse d'iteration la plus rapide possible.
  • Vous construisez un outil interne ou un produit SaaS ou l'API n'est pas exposee publiquement.
  • La surete de types est une priorite absolue et vous souhaitez detecter les incompatibilites d'API a la compilation.

Patrons d'architecture du monde reel

Approche hybride : tRPC + REST

De nombreuses equipes utilisent tRPC pour leur communication interne frontend-backend et exposent une REST API separee pour les integrations tierces :

// Routeur tRPC interne pour le tableau de bord
export const appRouter = router({
  user: userRouter,
  billing: billingRouter,
  analytics: analyticsRouter,
});

// REST API publique pour les integrations
app.get('/api/v1/users/:id', async (req, res) => {
  const user = await db.users.findUnique({
    where: { id: req.params.id },
    select: publicUserFields,
  });
  res.json(user);
});

GraphQL comme Gateway

GraphQL fonctionne bien comme un gateway d'API qui federe les donnees de plusieurs microservices :

// Schema gateway qui combine plusieurs services
const gatewaySchema = stitchSchemas({
  subschemas: [
    { schema: userServiceSchema, executor: userServiceExecutor },
    { schema: orderServiceSchema, executor: orderServiceExecutor },
    { schema: inventoryServiceSchema, executor: inventoryServiceExecutor },
  ],
});

REST avec OpenAPI pour la surete de types

Si vous choisissez REST mais souhaitez la surete de types, la generation de code OpenAPI comble le fosse :

# openapi.yaml
paths:
  /users/{id}:
    get:
      operationId: getUser
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
# Generer un client type
npx openapi-typescript-codegen --input openapi.yaml --output src/api
// Client genere avec surete de types complete
import { UserService } from './api';

const user = await UserService.getUser({ id: '42' });
// user est entierement type

Considerations de performance

Performance de REST

REST beneficie du multiplexage HTTP/2 et de la mise en cache du navigateur. Pour les APIs principalement en lecture servant du contenu pouvant etre mis en cache, REST peut etre extremement rapide. L'inconvenient est les multiples allers-retours quand vous avez besoin de donnees de plusieurs endpoints.

Performance de GraphQL

GraphQL reduit les allers-retours en permettant des requetes complexes en une seule requete. Cependant, le serveur doit resoudre des arbres de requetes potentiellement complexes. Sans optimisation appropriee (data loaders, limites de complexite de requete, persisted queries), GraphQL peut en realite etre plus lent que REST.

Performance de tRPC

tRPC a un overhead de serialisation minimal puisqu'il communique en utilisant du JSON simple sur HTTP. Avec le batching de requetes active, plusieurs appels de procedure sont combines en une seule requete HTTP, reduisant les allers-retours. L'integration React Query fournit une mise en cache cote client qui reduit encore les requetes reseau.

Conclusion

Il n'y a pas d'approche API unique qui soit la meilleure. Le bon choix depend de votre equipe, de votre stack technologique et des exigences de votre projet.

REST reste le choix par defaut pour les APIs publiques, la communication entre microservices et les equipes travaillant avec plusieurs langages. Sa simplicite et son support universel en font le choix sur pour la plupart des scenarios.

GraphQL excelle quand vous avez des donnees complexes et relationnelles et de multiples applications clientes qui ont besoin de vues differentes de ces donnees. Il ajoute de la complexite, mais la flexibilite qu'il offre peut en valoir la peine pour le bon cas d'utilisation.

tRPC est la meilleure option pour les projets monorepo TypeScript ou la velocite de developpement et la surete de types sont des priorites. Il elimine toute une categorie de bugs (incompatibilites de contrat d'API) et offre l'experience de developpement la plus rapide des trois.

De nombreuses equipes prosperes utilisent une combinaison de ces approches, tirant parti de chacune la ou elle a le plus de sens. L'important est de comprendre les compromis et de prendre une decision intentionnelle plutot que d'utiliser par defaut ce que votre dernier projet utilisait.

Articles associés