🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

Guia completo do tRPC 2026: APIs de tipo seguro com Next.js e React Query

⏱️5 min read  ·  898 words

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.

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çãotrpc.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.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা