Aller au contenu principal
Tous les articles
·3 min de lecture

tRPC v11 : une API full type-safe sans code generation

Zéro schéma GraphQL, zéro codegen, zéro runtime overhead — comment tRPC v11 change la façon de construire des API pour les SaaS Next.js.

Le problème avec REST et GraphQL

REST : vous écrivez votre API, puis vous écrivez les types côté client. Manuellement. À chaque changement. Et quand les types driftent... bug silencieux en production.

GraphQL : type safety, oui. Mais au prix d'un schéma à maintenir, un codegen à lancer, et un runtime qui parse chaque requête.

tRPC : vous écrivez votre API en TypeScript, et le client a les types automatiquement. Pas de codegen, pas de schéma, pas de runtime overhead.

Setup dans un SaaS Next.js

Le router tRPC

// src/server/api/routers/invoice.ts
import { z } from "zod";
import { createTRPCRouter, staffProcedure } from "~/server/api/trpc";
 
export const invoiceRouter = createTRPCRouter({
  getAll: staffProcedure.query(async ({ ctx }) => {
    return ctx.orgDb.invoice.findMany({
      orderBy: { createdAt: "desc" },
      include: { client: true },
    });
  }),
 
  create: staffProcedure
    .input(
      z.object({
        clientId: z.string(),
        items: z.array(
          z.object({
            description: z.string(),
            quantity: z.number().positive(),
            unitPrice: z.number().positive(),
          }),
        ),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      return ctx.db.invoice.create({
        data: {
          organizationId: ctx.session.user.organizationId,
          clientId: input.clientId,
          items: { create: input.items },
        },
      });
    }),
});

Côté client — zéro configuration

"use client";
import { api } from "~/trpc/react";
 
export function InvoiceList() {
  const { data, isLoading } = api.invoice.getAll.useQuery();
 
  if (isLoading) return <Skeleton />;
 
  return (
    <div>
      {data?.map((invoice) => (
        // invoice est typé automatiquement — autocomplétion complète
        <InvoiceCard key={invoice.id} invoice={invoice} />
      ))}
    </div>
  );
}

Changez le type de retour côté serveur → TypeScript vous signale immédiatement les erreurs côté client. Avant même de lancer l'app.

La hiérarchie des procédures

tRPC v11 permet de chaîner des middlewares pour créer des niveaux d'accès :

// Du moins restrictif au plus restrictif
export const publicProcedure = t.procedure;
 
export const protectedProcedure = publicProcedure.use(enforceAuth);
 
export const staffProcedure = protectedProcedure.use(enforceStaffRole);
 
export const adminProcedure = protectedProcedure.use(enforceAdminRole);
 
// Permission granulaire
export const requirePermission = (perm: string) =>
  staffProcedure.use(({ ctx, next }) => {
    if (!hasPermission(ctx.session.user.role, perm)) {
      throw new TRPCError({ code: "FORBIDDEN" });
    }
    return next({ ctx });
  });

Chaque router choisit le bon niveau. Une query publique ? publicProcedure. Un CRUD admin ? requirePermission("invoices:write").

Validation avec Zod

tRPC s'intègre nativement avec Zod pour la validation d'input :

.input(
  z.object({
    search: z.string().optional(),
    page: z.number().int().positive().default(1),
    perPage: z.number().int().min(1).max(100).default(20),
  })
)

Le type de input dans votre handler est inféré automatiquement depuis le schéma Zod. Un seul endroit pour la validation ET les types.

Mutations optimistes

const utils = api.useUtils();
 
const createInvoice = api.invoice.create.useMutation({
  onSuccess: () => {
    // Invalider le cache pour refetch
    utils.invoice.getAll.invalidate();
  },
});

Pourquoi pas GraphQL ?

CritèretRPCGraphQL
CodegenNonOui
Runtime overheadZéroParsing + résolution
Setup10 min30 min + tooling
Type safetyFullFull (avec codegen)
Cas d'usageSaaS monorepoAPI publique multi-client

Conclusion

tRPC v11 élimine toute une catégorie de bugs — les types driftés entre client et serveur. Dans un SaaS où la vélocité compte, c'est un gain de productivité énorme.

Partager
tRPC v11 : une API full type-safe sans code generation | HeartCo Dev Blog