·3 min de lecture
Next.js 15 + tRPC + Prisma : le trio gagnant pour votre SaaS
Setup pas à pas du trio technique le plus productif pour un SaaS en 2026 : App Router, type safety end-to-end, et ORM moderne.
nextjstrpcprismatutorial
Le problème : REST est mort (pour les SaaS)
Quand vous construisez un SaaS, vous passez un temps fou à maintenir la synchronisation entre votre API et votre frontend. Types dupliqués, validation dupliquée, documentation à jour... C'est du temps perdu.
tRPC élimine ce problème en partageant les types entre serveur et client automatiquement.
Setup : de zéro à productif
1. Structure du projet
src/
server/
api/
root.ts ← Registre des routers
trpc.ts ← Procédures et middlewares
routers/
invoice.ts ← Router facturation
client.ts ← Router clients
...
app/
dashboard/
facturation/
page.tsx ← Page React (Server Component)
2. Définir les procédures
Les procédures sont le cœur de tRPC. Elles remplacent les endpoints REST :
// src/server/api/trpc.ts
import { initTRPC, TRPCError } from "@trpc/server";
const t = initTRPC.context<Context>().create();
// Procédure publique — accessible sans auth
export const publicProcedure = t.procedure;
// Procédure protégée — authentification requise
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({ ctx: { session: ctx.session } });
});
// Procédure staff — rôle ≠ CLIENT
export const staffProcedure = protectedProcedure.use(({ ctx, next }) => {
if (ctx.session.user.role === "CLIENT") {
throw new TRPCError({ code: "FORBIDDEN" });
}
return next({ ctx });
});3. Créer un router
// src/server/api/routers/invoice.ts
import { z } from "zod";
import { createTRPCRouter, staffProcedure } from "~/server/api/trpc";
export const invoiceRouter = createTRPCRouter({
getAll: staffProcedure
.input(
z.object({
status: z.enum(["DRAFT", "SENT", "PAID"]).optional(),
}),
)
.query(async ({ ctx, input }) => {
return ctx.orgDb.invoice.findMany({
where: input.status ? { status: input.status } : undefined,
orderBy: { createdAt: "desc" },
include: { client: { select: { name: true } } },
});
}),
create: staffProcedure
.input(
z.object({
clientId: z.string(),
lines: z.array(
z.object({
description: z.string(),
quantity: z.number().positive(),
unitPrice: z.number().positive(),
}),
),
}),
)
.mutation(async ({ ctx, input }) => {
// ctx.orgDb auto-filtre par organizationId
return ctx.orgDb.invoice.create({
data: {
clientId: input.clientId,
lines: { create: input.lines },
authorId: ctx.session.user.id,
},
});
}),
});4. Appeler depuis le frontend
"use client";
import { api } from "~/trpc/react";
export function InvoiceList() {
const { data, isLoading } = api.invoice.getAll.useQuery({
status: "DRAFT",
});
if (isLoading) return <Skeleton />;
return (
<ul>
{data?.map((invoice) => (
<li key={invoice.id}>
{invoice.client.name} — {invoice.totalHT}€
</li>
))}
</ul>
);
}Remarquez : zéro type annoté côté client. TypeScript infère tout depuis le router.
Prisma : l'ORM qui change tout
Auto-scope multi-tenant
// ctx.orgDb filtre automatiquement par organizationId
const clients = await ctx.orgDb.client.findMany();
// SQL: SELECT * FROM "Client" WHERE "organizationId" = 'org_xxx'Migrations
# Ajouter un champ
npx prisma migrate dev --name add-invoice-due-date
# Régénérer le client typé
npx prisma generatePattern RBAC granulaire
// Permission-based access control
import { requirePermission } from "~/server/api/trpc";
export const invoiceRouter = createTRPCRouter({
// Seuls les users avec "facturation:create" peuvent créer
create: requirePermission("facturation:create")
.input(createInvoiceSchema)
.mutation(async ({ ctx, input }) => {
// ...
}),
// Lecture : permission "facturation:read"
getAll: requirePermission("facturation:read").query(async ({ ctx }) => {
// ...
}),
});Conclusion
Ce trio (Next.js 15 + tRPC + Prisma) vous donne :
- Type safety de la DB au composant React
- Zero boilerplate — pas de types dupliqués
- DX incroyable — autocomplétion partout
- Performance — Server Components + requêtes optimisées
Dans le prochain article : comment implémenter l'authentification multi-tenant avec NextAuth v5.