Build This Now
Build This Now
O que é o Código Claude?Instalar o Claude CodeInstalador Nativo do Claude CodeO Teu Primeiro Projeto com Claude Code
Failed Payment RecoveryLocalization and i18nPDF InvoicesReferral ProgramTwo-Factor AuthNext.js DevTools MCPNext.js Agent SetupSubscription BillingMulti-Tenant SaaSAI Chat FeatureRate LimitingStripe WebhooksSemantic SearchRealtime UpdatesDrip Email Sequencesv2.1.122 Release NotesClaude Code Dynamic Workflows: Como Orquestrar 1.000 Subagentes Num Codebase RealMelhores Práticas do Claude CodeBoas Práticas para o Claude Opus 4.7Claude Code num VPSIntegração GitRevisão de Código com ClaudeWorktrees no Claude CodeControle Remoto do Claude CodeChannels do Claude CodeChannels, Routines, Teleport, DispatchTarefas Agendadas no Claude CodePermissões do Claude CodeModo Auto do Claude CodeAdicionar Pagamentos Stripe Com o Claude CodeFeedback LoopsFluxos de Trabalho com TodosTarefas no Claude CodeTemplates de ProjetoPreços e Consumo de Tokens no Claude CodePreços do Claude Code: O Que Vais Mesmo PagarClaude Code Ultra ReviewConstruir Uma App Next.js Com o Claude CodeSupabase DatabaseVercel DeepsecTest-Driven DevelopmentComo Construir um MVP de SaaS Com o Claude CodeAdicionar Autenticação Com o Claude Code (Supabase Auth)Adicionar Email Transacional Com o Claude Code (Resend + React Email)Construir Uma API Type-Safe Com o Claude Code (oRPC + Zod)File UploadsBackground Jobs (Inngest)Admin DashboardFull-Text SearchClaude Agent SDKKiro Migration GuideClaude Research AgentLeave Grok BuildRoute Subagent ModelsClaude Monorepo SetupCI Repair AgentVisual Regression TestsAgent Cost DashboardCursor Migration GuideComércio Agêntico: Como Construir uma App Que Agentes de IA Podem Pagar1M ContextUser API KeysAudit LogsCSV Import PipelineDatabase MigrationsProduction Error TrackingFeature FlagsGitHub ActionsHeadless ModeMax Plan vs APICaching and RevalidationIn-App NotificationsOutbound WebhooksPrompt CachingRoles & PermissionsMarketplace PaymentsUsage-Based BillingQuanto Custa Construir um SaaS com Claude Code em 2026Parallel AI AgentsCoding Agent Injection
speedy_devvkoen_salo
Blog/Handbook/Workflow/Type-Safe API

Construir Uma API Type-Safe Com o Claude Code (oRPC + Zod)

Como construir uma camada de API totalmente type-safe em Next.js 16 com oRPC e Zod, usando o Claude Code, para que as mudanças de esquema partam a build em vez da produção.

Quer o framework por trás destes projetos?

Obtenha o sistema Claude Code que usamos para planejar, construir, testar e lançar software em produção.

Veja o que construímos para empresas →
speedy_devvkoen_salo
speedy_devvWritten by speedy_devvPublished Jul 14, 202610 min readHandbook hubWorkflow index

A versão de uma API type-safe que interessa mesmo não é um diagrama, é isto: renomeias um campo no teu esquema Zod, e o componente cliente que o lê fica vermelho no teu editor antes sequer de teres guardado o ficheiro. Nenhuma coleção do Postman para atualizar. Nenhum 500 em runtime na produção três dias depois. O erro aparece no exato momento em que partiste algo, no exato ficheiro que precisas de corrigir.

É isso que o oRPC mais o Zod te dão numa app Next.js 16, e o Claude Code é uma forma rápida de o ligar corretamente à primeira.

O bug que esta configuração evita

O modo de falha comum numa rota de API típica do Next.js é este: defines um route handler que devolve { id, title, done }. Noutro sítio qualquer, um componente desestrutura task.completed porque alguém renomeou o campo no servidor há seis semanas e ninguém atualizou o cliente. O TypeScript não o consegue apanhar, porque o fetch() devolve any (ou um tipo que escreveste à mão e em que agora confias às cegas). O bug vai para produção. Descobre-lo por um alerta do Sentry, não por um compilador.

"Type-safe da base de dados ao frontend" significa que há exatamente uma fonte da verdade para o formato dos teus dados, um esquema Zod, e tanto o teu handler no servidor como a tua chamada no cliente são tipados a partir desse mesmo esquema. Muda o esquema, e todos os sítios por onde esse formato passa acendem-se com um erro de tipos até os corrigires.

Instalar o oRPC e o Zod

O oRPC vem em alguns pacotes pequenos: um pacote de servidor para definir procedimentos e montá-los como um handler, e um pacote de cliente para os chamar com inferência completa. Instala ambos ao lado do Zod.

npm install @orpc/server @orpc/client zod

Nada mais é preciso. Nenhum passo de geração de código, nenhuma CLI para correr depois de cada mudança de esquema. Os tipos vêm de o TypeScript inferir através do esquema Zod, não de um artefacto de build que tens de te lembrar de regenerar.

Definir o esquema

Começa pelo formato da coisa que a tua API devolve. Isto é uma pequena API de lista de tarefas, um esquema de objeto Zod para uma tarefa, e um esquema derivado para o que um pedido de "criar" aceita (apenas o título, já que o id e o done são definidos no servidor).

// lib/schemas/task.ts
import * as z from "zod";

export const taskSchema = z.object({
  id: z.string(),
  title: z.string().min(1).max(200),
  done: z.boolean(),
});

export const createTaskInput = taskSchema.pick({ title: true });

export type Task = z.infer<typeof taskSchema>;

O z.infer é que faz o trabalho de verdade aqui. O Task não é um tipo que mantenhas à mão, é derivado do esquema, por isso nunca pode dessincronizar-se do validador em runtime. Se adicionares um campo ao taskSchema, o Task apanha-o automaticamente.

Para o tutorial, a "base de dados" é um array em memória. Troca-a por queries reais de Supabase ou Postgres numa app de produção, o resto da configuração não muda.

// lib/db/tasks.ts
import { randomUUID } from "crypto";
import type { Task } from "@/lib/schemas/task";

const tasks: Task[] = [
  { id: randomUUID(), title: "Wire up the API layer", done: false },
];

export async function listTasks(): Promise<Task[]> {
  return tasks;
}

export async function createTask(title: string): Promise<Task> {
  const task: Task = { id: randomUUID(), title, done: false };
  tasks.push(task);
  return task;
}

Definir um procedimento oRPC

Um procedimento no oRPC é construído com os, um builder encadeável do @orpc/server. Anexas um esquema de input, um esquema de output, e uma função handler. Ambas as chamadas de esquema são opcionais, mas anexar as duas é o que te dá validação à entrada e um formato tipado e verificado à saída.

// lib/orpc/router.ts
import * as z from "zod";
import { os } from "@orpc/server";
import { taskSchema, createTaskInput } from "@/lib/schemas/task";
import { listTasks, createTask } from "@/lib/db/tasks";

const list = os.output(z.array(taskSchema)).handler(async () => {
  return listTasks();
});

const create = os
  .input(createTaskInput)
  .output(taskSchema)
  .handler(async ({ input }) => {
    return createTask(input.title);
  });

export const router = {
  tasks: {
    list,
    create,
  },
};

export type AppRouter = typeof router;

Duas coisas que vale a pena reparar. Primeiro, o input dentro do handler create já está tipado como { title: string }, sem cast, sem interface manual. Segundo, se o createTask devolvesse algo que não corresponde ao taskSchema, o TypeScript assinala-o logo ali no handler, antes de o código sequer correr. O esquema de output é um contrato sobre o código do teu próprio servidor, não só sobre o cliente.

Montar o router como um Route Handler

O oRPC expõe um handler RPC que fala fetch Request/Response puro, o que mapeia diretamente num Route Handler do Next.js 16. Monta-o num segmento catch-all para que todos os procedimentos do router fiquem acessíveis sob um único prefixo.

// app/rpc/[[...rest]]/route.ts
import { RPCHandler } from "@orpc/server/fetch";
import { router } from "@/lib/orpc/router";

const handler = new RPCHandler(router);

async function handleRequest(request: Request) {
  const { response } = await handler.handle(request, {
    prefix: "/rpc",
    context: {},
  });

  return response ?? new Response("Not found", { status: 404 });
}

export const GET = handleRequest;
export const POST = handleRequest;
export const PUT = handleRequest;
export const PATCH = handleRequest;
export const DELETE = handleRequest;

Todos os verbos HTTP apontam para a mesma função handleRequest porque o handler RPC descobre qual o procedimento a chamar a partir do próprio pedido. Não escreves um ficheiro separado por endpoint, e não mapeias URLs para funções à mão. Adicionar um novo procedimento ao router em lib/orpc/router.ts é o diff inteiro.

Chamá-lo a partir de um Server Component

Dentro de um Server Component, não há razão para fazer uma ida e volta por HTTP para chamar o teu próprio servidor. O cliente do lado do servidor do oRPC (createRouterClient) chama a função handler do procedimento diretamente, no mesmo processo, mantendo exatamente a mesma interface tipada do cliente de rede.

// lib/orpc/server-client.ts
import "server-only";
import { createRouterClient } from "@orpc/server";
import { router } from "@/lib/orpc/router";

export const orpc = createRouterClient(router, {
  context: {},
});

O import server-only é uma proteção, lança um erro de build se este ficheiro for alguma vez puxado para código de cliente por engano. Usa-o numa página:

// app/tasks/page.tsx
import { orpc } from "@/lib/orpc/server-client";

export default async function TasksPage() {
  const tasks = await orpc.tasks.list();

  return (
    <main className="max-w-lg mx-auto py-12">
      <h1 className="text-2xl font-bold mb-6">Tasks</h1>
      <ul className="space-y-2">
        {tasks.map((task) => (
          <li key={task.id}>{task.title}</li>
        ))}
      </ul>
    </main>
  );
}

O tasks é Task[], inferido todo o caminho a partir do taskSchema. Nenhum tipo de retorno manual no TasksPage, nenhum any a entrar sorrateiramente de uma chamada fetch sem tipo.

Chamá-lo a partir de um Client Component

O browser não consegue chamar as tuas funções de servidor no mesmo processo, por isso o cliente do lado do cliente vai pela rede, usando o link RPC apontado para a rota que montaste antes. A interface é idêntica à do cliente do lado do servidor, os mesmos nomes de métodos, os mesmos formatos de argumentos, os mesmos tipos de retorno inferidos.

// lib/orpc/client.ts
import { createORPCClient } from "@orpc/client";
import { RPCLink } from "@orpc/client/fetch";
import type { RouterClient } from "@orpc/server";
import type { AppRouter } from "@/lib/orpc/router";

const link = new RPCLink({
  url: "/rpc",
});

export const orpc: RouterClient<AppRouter> = createORPCClient(link);

Usa-o num formulário. O title é uma string simples do useState, e o valor de retorno de orpc.tasks.create(...) é uma Task totalmente tipada, não uma Response a que tens de fazer .json() e cast.

// components/new-task-form.tsx
"use client";

import { useState } from "react";
import { orpc } from "@/lib/orpc/client";

export function NewTaskForm() {
  const [title, setTitle] = useState("");
  const [pending, setPending] = useState(false);

  async function handleSubmit(event: React.FormEvent) {
    event.preventDefault();
    setPending(true);
    const task = await orpc.tasks.create({ title });
    setPending(false);
    setTitle("");
    console.log("created task", task.id, task.done);
  }

  return (
    <form onSubmit={handleSubmit} className="flex gap-2">
      <input
        value={title}
        onChange={(event) => setTitle(event.target.value)}
        className="border rounded px-3 py-2 flex-1"
        placeholder="New task"
      />
      <button
        type="submit"
        disabled={pending}
        className="bg-black text-white rounded px-4 py-2"
      >
        Add
      </button>
    </form>
  );
}

Se tentasses passar { title, done: false } para orpc.tasks.create, o TypeScript rejeitava-o antes de correres seja o que for, porque o createTaskInput só aceita title. É o esquema de input a fazer o seu trabalho do lado do cliente, não só do lado do servidor.

Ver o compilador apanhar uma mudança que parte tudo

Esta é a parte que vale mesmo a pena experimentar. Abre o lib/schemas/task.ts e renomeia done para completed:

export const taskSchema = z.object({
  id: z.string(),
  title: z.string().min(1).max(200),
  completed: z.boolean(),
});

Guarda o ficheiro. O NewTaskForm mostra agora um erro de compilação em task.done: Property 'done' does not exist on type '{ id: string; title: string; completed: boolean }'. O lib/db/tasks.ts também dá erro, já que o createTask continua a construir um objeto com uma chave done. Nada corre até cada um deles estar corrigido.

É esse o argumento inteiro. O esquema mudou uma vez, num ficheiro, e o TypeScript encontrou todos os sítios a jusante que assumiam o formato antigo. Nenhum grep por .done em todo o código, nenhum erro em runtime dias depois quando um utilizador real bate no caminho de código desatualizado.

Usar o Claude Code para ligar isto

Pede ao Claude Code para adicionar um novo procedimento e ele segue o padrão assim que o teu CLAUDE.md declara a convenção de forma clara:

## API Layer

- oRPC procedures live in lib/orpc/router.ts
- Every procedure gets both .input() and .output() with a Zod schema
- Zod schemas for API-facing shapes live in lib/schemas/
- Route handler is mounted at app/rpc/[[...rest]]/route.ts, don't create new route files per endpoint

Com isso no sítio, "add a procedure to mark a task done" produz uma nova entrada no mesmo objeto router, uma atualização de esquema se for preciso, e um handler que corresponde ao estilo existente, em vez de uma rota de API ad-hoc nova com os seus próprios tipos escritos à mão.

O Code Kit de $29 assenta exatamente neste padrão. É uma estrutura por cima do Claude Code, não uma framework separada, e a camada de API que ele gera é oRPC com Zod por omissão, type-safe desde as tuas queries à base de dados até ao componente que renderiza os dados. Continuas a precisar de um plano Anthropic pago para correr o próprio Claude Code. O que a estrutura acrescenta é a convenção já ligada, para que cada nova funcionalidade que ela constrói receba esta mesma garantia sem teres de a pedir de cada vez.

Posted by @speedy_devv

Continue in Workflow

  • Comércio Agêntico: Como Construir uma App Que Agentes de IA Podem Pagar
    Um guia em português simples sobre comércio agêntico em 2026: o que fazem o x402, o ACP e o Machine Payments Protocol, mais um passo a passo de fim de semana para lançar uma API paga que agentes de IA podem comprar.
  • Melhores Práticas do Claude Code
    Cinco hábitos separam os engenheiros que entregam com Claude Code: PRDs, regras modulares em CLAUDE.md, slash commands personalizados, resets com /clear e uma mentalidade de evolução do sistema.
  • Modo Auto do Claude Code
    Um segundo modelo Sonnet revê cada chamada de ferramenta do Claude Code antes de ser executada. O que o modo auto bloqueia, o que permite e as regras de permissão que cria nas tuas definições.
  • Channels, Routines, Teleport, Dispatch
    As quatro funcionalidades de Claude Code que a Anthropic lançou em março e abril de 2026 e que transformam a CLI numa camada de coordenação orientada a eventos entre telemóvel, web e desktop.
  • Claude Code 1M Context in Practice: When Bigger Isn't Better
    The 1M-token context window is GA at flat pricing, but bigger isn't always better. A decision framework, token-cost math, and when to use /compact, subagents, and dynamic workflows instead.
  • How to Build an Admin Dashboard With Claude Code
    Ship an internal admin panel with Claude Code: role-gated routes, a searchable users and orders table with pagination, impersonation-safe RLS, and metrics tiles pulled through a type-safe API.

More from Handbook

  • Best SaaS Boilerplate 2026: The Honest Comparison
    An honest 2026 roundup of the best SaaS boilerplates and starter kits (ShipFast, Makerkit, Supastarter, SaaS Pegasus, Divjoy, open source), with real pricing, stacks, and the trade-off nobody mentions: a boilerplate still leaves you coding.
  • Changelog do Claude Code
    Notas de cada versão do Claude Code desde a beta v0.2 até março de 2026. Bare mode, relay de permissões com Channels, correções de OAuth e todas as breaking changes.
  • What It Really Costs to Build a SaaS MVP in 2026
    A buyer's cost breakdown for building a SaaS MVP in 2026. Real freelancer, agency, no-code, AI-tool, and DIY numbers with citations, plus where the money actually goes.
  • Templates de Prompts que Entregam Código
    Dez receitas de prompts que entregam código: scaffolding full-stack, APIs, schemas, testes, refatorações, debugging, reviews e CI. Cada uma com os erros a evitar.

Quer o framework por trás destes projetos?

Obtenha o sistema Claude Code que usamos para planejar, construir, testar e lançar software em produção.

Veja o que construímos para empresas →
speedy_devvkoen_salo

Adicionar Email Transacional Com o Claude Code (Resend + React Email)

Como construir emails de boas-vindas, recibos e reposição de password numa app Next.js 16 usando o Resend e o React Email, com o Claude Code a escrever os templates e a lógica de envio.

File Uploads

Build secure user file and image uploads end to end with Claude Code: private Supabase Storage buckets, per-user RLS, signed URLs, and an upload UI wired through a type-safe oRPC API.

On this page

O bug que esta configuração evita
Instalar o oRPC e o Zod
Definir o esquema
Definir um procedimento oRPC
Montar o router como um Route Handler
Chamá-lo a partir de um Server Component
Chamá-lo a partir de um Client Component
Ver o compilador apanhar uma mudança que parte tudo
Usar o Claude Code para ligar isto

Quer o framework por trás destes projetos?

Obtenha o sistema Claude Code que usamos para planejar, construir, testar e lançar software em produção.

Veja o que construímos para empresas →