Apogée Consult
Retour au blog
Mathieu Ponton
Mathieu PontonCo-Founder & ingénieur logiciel

Je suis Mathieu Ponton, Co-Founder & ingénieur logiciel chez Apogée Consult à Lyon. Ingénieur diplômé de Polytech Lyon (Informatique), j'ai fait trois ans en apprentissage, partagés entre la Métropole de Lyon (inclusion numérique avec Res'in et sobriété énergétique avec Écolyo) et Superwyze, une startup medtech (POCs, dont certains aujourd'hui industrialisés, et travail sur des codebases existantes). J'ai livré plus de 10 projets en production (web, mobile et IA / RAG) pour des PME, startups et organisations publiques.

tRPC vs REST en 2026 : pourquoi on est resté sur REST pour nos APIs publiques

  • archi

tRPC offre une sécurité de types bout en bout séduisante. Nous l'avons évalué sérieusement. Voici pourquoi nos APIs publiques restent en REST, et où tRPC garde sa place.

tRPC vs REST en 2026 : pourquoi on est resté sur REST pour nos APIs publiques

tRPC a changé la façon dont beaucoup d'équipes TypeScript pensent leurs APIs internes. L'idée est honnêtement séduisante : un seul type défini côté serveur, inféré automatiquement côté client, sans génération de code, sans OpenAPI. Nous avons utilisé tRPC sur deux projets. Sur nos APIs publiques, nous sommes revenus à REST. Voici l'analyse.

Ce que tRPC apporte vraiment

Sur un projet Next.js full-stack où le front et le back sont dans le même repo et déployés ensemble, tRPC est difficile à battre pour la productivité.

// Serveur, on définit le router
const userRouter = router({
  getProfile: protectedProcedure
    .input(z.object({ userId: z.string().uuid() }))
    .query(async ({ input, ctx }) => {
      return db.query.users.findFirst({
        where: eq(users.id, input.userId),
      });
    }),
});

// Client, type inféré, aucune génération de code
const { data } = trpc.user.getProfile.useQuery({ userId: "abc-123" });
// data est typé UserProfile | undefined, pas any

Le gain est réel. Pas de décalage entre le type retourné par le back et ce que le front consomme. Pas d'étape de génération de client (openapi-typescript, swagger-codegen) à maintenir dans la CI. Les refactoring de l'API se propagent immédiatement en erreurs TypeScript côté client.

Sur un projet interne avec une seule équipe et un seul client TypeScript, c'est un avantage net.

Où ça se complique

Le contrat API n'est pas un artefact exportable

REST + OpenAPI produit un fichier de spécification lisible par n'importe quel outil. Postman, Insomnia, les clients générés pour Python ou Go, les équipes externes qui consomment l'API. La spec OpenAPI est un contrat public.

tRPC n'a pas d'équivalent natif. Il existe trpc-openapi qui génère une spec à partir d'un router tRPC, mais c'est un outil tiers avec ses propres contraintes (les procédures doivent respecter des conventions de nommage supplémentaires, les types complexes ne passent pas toujours bien).

Sur l'un de nos projets, un client avait une équipe mobile iOS qui consommait notre API. Leur développeur Swift voulait une spec OpenAPI pour générer son client. Avec tRPC pur, nous aurions dû maintenir une couche de traduction ou dupliquer la définition de l'API.

Le versioning est complexe

REST a des conventions établies pour le versioning : préfixe /v1/, headers Accept-Version, déprécation progressive. Elles ne sont pas parfaites, mais elles sont connues.

tRPC gère mal le versioning. Un changement cassant dans un router tRPC est détectable à la compilation si le client est dans le même repo. Mais si des clients externes consomment votre API tRPC via son exposition HTTP (les procédures sont accessibles via GET/POST sur des URLs prévisibles), un changement de signature casse silencieusement les clients non-TypeScript.

Les outils de debug et de monitoring

Les requêtes tRPC passent par POST sur une URL unique (/api/trpc/user.getProfile). Les outils d'observabilité qui comprennent les méthodes HTTP et les paths REST (Datadog APM, certains proxies) agrègent toutes les requêtes tRPC sur le même endpoint. Il faut instrumenter manuellement.

Avec REST, le path /api/users/{id} est déjà un signal sémantique utile dans les logs et les dashboards.

GraphQL dans l'équation

On ne peut pas comparer tRPC et REST sans mentionner GraphQL. tRPC et GraphQL résolvent des problèmes similaires (typage, flexibilité des queries) avec des approches différentes.

GraphQL a une spec mature, un écosystème large, et des outils de sécurité établis (depth limiting, query complexity). Il gère bien le versioning via la déprécation de champs. Il nécessite plus de boilerplate initial (schema, resolvers).

tRPC est beaucoup plus simple à démarrer si votre stack est 100 % TypeScript. Il devient un choix naturel dans un contexte Next.js + backend TypeScript without mobile clients.

Notre position : GraphQL est justifié pour des APIs avec des besoins de query flexibles et des clients hétérogènes. tRPC est justifié pour des apps full-stack TypeScript sans clients externes. REST reste la valeur sûre quand le contrat API est public ou partagé avec des équipes non-TypeScript.

Ce que nous utilisons en pratique

Pour nos APIs publiques : REST + Zod + OpenAPI

Nous définissons les types Zod au niveau des handlers. Nous générons la spec OpenAPI avec @asteasolutions/zod-to-openapi. Le client TypeScript du front est généré depuis cette spec avec openapi-typescript.

// Définition du schéma une seule fois
const UserProfileSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  plan: z.enum(["starter", "pro", "enterprise"]),
});

// Utilisé pour la validation du handler ET pour la génération OpenAPI
registry.registerPath({
  method: "get",
  path: "/users/{id}",
  responses: {
    200: {
      description: "User profile",
      content: { "application/json": { schema: UserProfileSchema } },
    },
  },
});

Le type TypeScript côté client est généré en CI depuis la spec. Ce n'est pas aussi transparent que tRPC, mais c'est plus robuste pour un contrat partagé.

Pour nos apps internes : tRPC

Sur nos outils internes (dashboard admin, outils de backoffice), où le front et le back sont colocalisés et où il n'y aura jamais de client externe, nous utilisons tRPC. La productivité est nettement meilleure.

Le vrai critère de décision

La question n'est pas "tRPC est-il bon". Il l'est. La question est : "Est-ce que ce contrat API sera consommé par autre chose qu'un client TypeScript que je contrôle ?"

Si la réponse est non, tRPC est probablement le bon choix. Si la réponse est oui, clients mobiles, équipes externes, intégrations tierces, SDK publique, REST reste plus adapté parce que son écosystème de documentation, de validation, et de consommation est universel.

La promesse de sécurité de types bout en bout n'a de valeur que si tous les bouts sont TypeScript.

Quel critère a guidé votre choix entre tRPC et REST sur votre dernier projet ? La question du versioning a-t-elle pesé dans la décision ?

Disponible pour de nouveaux projets

Un projet à concrétiser ?
Parlons-en, sans engagement.

Un échange de 30 minutes pour cadrer votre besoin, qualifier la faisabilité et vous proposer une trajectoire claire.

1// kick-off : réponse sous 24h
2const project = await apogee.scope({
3 type: 'web | mobile | IA',
4 timeline: '6 à 16 semaines',
5 approach: 'sur-mesure'
6})
7// → cadrage offert
tRPC vs REST en 2026 : notre choix argumenté | Apogée Consult