tRPC é a camada de API de ponta a ponta com segurança de tipo que elimina a necessidade de contratos de API, geração de código e duplicação de tipo entre o back-end e o front-end do TypeScript. Em 2026, tRPC v11 com integração React Query e suporte Next.js App Router se tornou o padrão para aplicativos TypeScript full-stack. Este guia cobre tudo, desde a configuração até os padrões de produção.
📋 Table of Contents
Por que tRPC?
- Contrato de API zero— tipos de backend disponíveis automaticamente no frontend
- Sem geração de código— tipos inferidos em tempo de compilação, não gerados
- TypeScript completo— preenchimento automático para chamadas de API, argumentos e tipos de retorno
- Consulta React integrada– cache, carregamento de estados, busca automática de trabalho
- Mesmo DX da chamada de função —
trpc.user.getById.useQuery(1)
Configuração com roteador de aplicativo Next.js
npm install @trpc/server @trpc/client @trpc/react-query @trpc/next
npm install @tanstack/react-query
npm install zod
src/
server/
api/
trpc.ts — tRPC base config
root.ts — root router
routers/
user.ts
post.ts
trpc/
react.tsx — client setup
server.ts — server-side caller
Configuração do servidor
// server/api/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { ZodError } from 'zod';
// Context type — available in all procedures
export interface Context {
userId: string | null;
db: PrismaClient;
}
const t = initTRPC.context<Context>().create({
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});
export const router = t.router;
export const publicProcedure = t.procedure;
// Auth middleware
const isAuthenticated = t.middleware(({ ctx, next }) => {
if (!ctx.userId) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({ ctx: { ...ctx, userId: ctx.userId } });
});
export const protectedProcedure = t.procedure.use(isAuthenticated);
// Rate limiting middleware
const withRateLimit = t.middleware(async ({ ctx, next }) => {
const key = `ratelimit:${ctx.userId ?? 'anonymous'}`;
const count = await redis.incr(key);
if (count === 1) await redis.expire(key, 60);
if (count > 100) throw new TRPCError({ code: 'TOO_MANY_REQUESTS' });
return next();
});
export const rateLimitedProcedure = protectedProcedure.use(withRateLimit);
Roteadores
// server/api/routers/user.ts
import { z } from 'zod';
import { router, publicProcedure, protectedProcedure } from '../trpc';
export const userRouter = router({
// Query — get data
getById: publicProcedure
.input(z.number().int().positive())
.query(async ({ ctx, input }) => {
const user = await ctx.db.user.findUnique({ where: { id: input } });
if (!user) throw new TRPCError({ code: 'NOT_FOUND', message: `User ${input} not found` });
return user;
}),
// Query with pagination
list: publicProcedure
.input(z.object({
page: z.number().default(1),
limit: z.number().max(100).default(20),
search: z.string().optional(),
}))
.query(async ({ ctx, input }) => {
const { page, limit, search } = input;
const [users, total] = await ctx.db.$transaction([
ctx.db.user.findMany({
where: search ? { name: { contains: search, mode: 'insensitive' } } : undefined,
skip: (page - 1) * limit,
take: limit,
}),
ctx.db.user.count(),
]);
return { users, total, page, limit };
}),
// Mutation — create/update/delete
create: protectedProcedure
.input(z.object({
name: z.string().min(2).max(50),
email: z.string().email(),
}))
.mutation(async ({ ctx, input }) => {
return ctx.db.user.create({ data: input });
}),
update: protectedProcedure
.input(z.object({
id: z.number(),
name: z.string().optional(),
email: z.string().email().optional(),
}))
.mutation(async ({ ctx, input }) => {
const { id, ...data } = input;
return ctx.db.user.update({ where: { id }, data });
}),
// Subscription — real-time
onUserCreated: protectedProcedure.subscription(({ ctx }) => {
return observable<User>((emit) => {
const unsub = eventEmitter.on('user.created', (user) => {
emit.next(user);
});
return () => unsub();
});
}),
});
Roteador Raiz
// server/api/root.ts
import { router } from './trpc';
import { userRouter } from './routers/user';
import { postRouter } from './routers/post';
export const appRouter = router({
user: userRouter,
post: postRouter,
});
export type AppRouter = typeof appRouter;
Manipulador de roteador de aplicativo Next.js
// app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '@/server/api/root';
import { createContext } from '@/server/api/context';
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext,
});
export { handler as GET, handler as POST };
Uso do cliente em React
// trpc/react.tsx
'use client';
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '@/server/api/root';
export const trpc = createTRPCReact<AppRouter>();
// components/UserList.tsx
'use client';
import { trpc } from '@/trpc/react';
export function UserList() {
const { data, isLoading, error } = trpc.user.list.useQuery({
page: 1,
limit: 20,
search: 'alice',
});
const createUser = trpc.user.create.useMutation({
onSuccess: () => {
utils.user.list.invalidate(); // refetch list
},
});
const utils = trpc.useUtils();
if (isLoading) return <Spinner />;
if (error) return <Error message={error.message} />;
return (
<div>
{data?.users.map(user => <UserCard key={user.id} user={user} />)}
<button onClick={() => createUser.mutate({ name: 'Bob', email: 'bob@example.com' })}>
{createUser.isPending ? 'Creating...' : 'Add User'}
</button>
</div>
);
}
// Server component usage (no hook)
import { api } from '@/trpc/server';
export default async function Page() {
const users = await api.user.list.query({ page: 1, limit: 10 });
return <UserList initialData={users} />;
}
tRPC em 2026 é o padrão ouro para segurança de tipo TypeScript full-stack. A melhoria do DX é dramática: seu IDE completa automaticamente argumentos de API e retorna tipos sem qualquer etapa de geração de código. Combinado com Zod para validação de entrada e Next.js App Router, o tRPC cria a experiência full-stack TypeScript mais compacta possível. Use-o para qualquer projeto onde o TypeScript seja usado no cliente e no servidor.
🔗 Share this article
✍️ Leave a Comment