Build This Now
Build This Now
Was ist der Claude Code?Claude Code installierenClaude Code Native InstallerDein erstes Claude Code-Projekt
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: 1.000 Subagents auf einer echten Codebase orchestrierenClaude Code Best PracticesClaude Opus 4.7 Best PracticesClaude Code auf einem VPSGit-IntegrationClaude Code ReviewClaude Code WorktreesClaude Code Remote ControlClaude Code ChannelsChannels, Routines, Teleport, DispatchGeplante Aufgaben mit Claude CodeClaude Code BerechtigungenClaude Code Auto-ModusStripe-Zahlungen mit Claude Code einbauenFeedback-LoopsTodo-WorkflowsClaude Code TasksProjekt-TemplatesClaude Code Preise und Token-NutzungClaude Code Preise: Was du wirklich zahlstClaude Code Ultra ReviewEine Next.js-App mit Claude Code bauenSupabase DatabaseVercel DeepsecTest-Driven DevelopmentSo baust du ein SaaS-MVP mit Claude CodeAuthentifizierung mit Claude Code einbauen (Supabase Auth)Transaktions-E-Mails mit Claude Code einbauen (Resend + React Email)Eine typsichere API mit Claude Code bauen (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 GuideAgentic Commerce: Wie du eine App baust, für die KI-Agents bezahlen können1M ContextUser API KeysAudit LogsCSV Import PipelineDatabase MigrationsProduction Error TrackingFeature FlagsGitHub ActionsHeadless ModeMax Plan vs APICaching and RevalidationIn-App NotificationsOutbound WebhooksPrompt CachingRoles & PermissionsMarketplace PaymentsUsage-Based BillingWas es 2026 kostet, ein SaaS mit Claude Code zu bauenParallel AI AgentsCoding Agent Injection
speedy_devvkoen_salo
Blog/Handbook/Workflow/Type-Safe API

Eine typsichere API mit Claude Code bauen (oRPC + Zod)

So baust du eine vollständig typsichere API-Schicht in Next.js 16 mit oRPC und Zod, mit Claude Code, sodass Schema-Änderungen den Build brechen statt der Produktion.

Sie möchten das Framework hinter diesen Projekten?

Holen Sie sich das Claude Code System, mit dem wir produktionsreife Software planen, bauen, testen und ausliefern.

Sehen Sie, was wir für Unternehmen bauen →
speedy_devvkoen_salo
speedy_devvWritten by speedy_devvPublished Jul 14, 202610 min readHandbook hubWorkflow index

Die Version einer typsicheren API, die wirklich zählt, ist kein Diagramm, sie ist das hier: Du benennst ein Feld in deinem Zod-Schema um, und die Client-Komponente, die es liest, wird in deinem Editor rot, bevor du die Datei überhaupt gespeichert hast. Keine Postman-Collection zu aktualisieren. Kein Runtime-500 in Produktion drei Tage später. Der Fehler taucht genau in dem Moment auf, in dem du etwas kaputt gemacht hast, in genau der Datei, die du fixen musst.

Genau das gibt dir oRPC plus Zod in einer Next.js-16-App, und Claude Code ist ein schneller Weg, es im ersten Anlauf korrekt zu verkabeln.

Der Bug, den dieses Setup verhindert

Der übliche Fehlermodus in einer typischen Next.js-API-Route sieht so aus: Du definierst einen Route Handler, der { id, title, done } zurückgibt. Irgendwo anders destrukturiert eine Komponente task.completed, weil jemand das Feld vor sechs Wochen auf dem Server umbenannt hat und niemand den Client aktualisiert hat. TypeScript kann es nicht abfangen, weil fetch() any zurückgibt (oder einen Typ, den du von Hand geschrieben hast und dem du jetzt blind vertraust). Der Bug geht live. Du erfährst es aus einem Sentry-Alert, nicht von einem Compiler.

"Typsicher von der Datenbank bis zum Frontend" heißt, es gibt genau eine Source of Truth für die Form deiner Daten, ein Zod-Schema, und sowohl dein Server-Handler als auch dein Client-Call sind von genau diesem Schema getypt. Änder das Schema, und jede Stelle, durch die diese Form fließt, leuchtet mit einem Type-Error auf, bis du sie fixst.

oRPC und Zod installieren

oRPC kommt als ein paar kleine Pakete: ein Server-Paket, um Procedures zu definieren und sie als Handler zu mounten, und ein Client-Paket, um sie mit voller Inferenz aufzurufen. Installier beide neben Zod.

npm install @orpc/server @orpc/client zod

Mehr braucht es nicht. Kein Code-Generation-Schritt, keine CLI, die man nach jeder Schema-Änderung laufen lassen muss. Die Typen kommen daher, dass TypeScript durch das Zod-Schema inferiert, nicht aus einem Build-Artefakt, an dessen Neugenerierung du denken musst.

Das Schema definieren

Starte mit der Form des Dings, das deine API zurückgibt. Das ist eine kleine Task-List-API, ein Zod-Objekt-Schema für eine Task und ein abgeleitetes Schema dafür, was ein "Create"-Request akzeptiert (nur der Titel, da id und done serverseitig gesetzt werden).

// 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>;

z.infer macht hier die eigentliche Arbeit. Task ist kein Typ, den du von Hand pflegst, er ist vom Schema abgeleitet, kann also nie mit dem Runtime-Validator aus dem Takt geraten. Wenn du taskSchema ein Feld hinzufügst, nimmt Task es automatisch auf.

Fürs Tutorial ist die "Datenbank" ein In-Memory-Array. Tausch das in einer Produktions-App gegen echte Supabase- oder Postgres-Queries, der Rest des Setups ändert sich nicht.

// 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;
}

Eine oRPC-Procedure definieren

Eine Procedure in oRPC wird mit os gebaut, einem verkettbaren Builder aus @orpc/server. Du hängst ein Input-Schema, ein Output-Schema und eine Handler-Funktion an. Beide Schema-Calls sind optional, aber beide anzuhängen ist das, was dir Validierung auf dem Weg rein und eine getypte, geprüfte Form auf dem Weg raus verschafft.

// 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;

Zwei Dinge, die auffallen. Erstens ist input innerhalb des create-Handlers schon als { title: string } getypt, kein Cast, kein manuelles Interface. Zweitens, wenn createTask etwas zurückgäbe, das nicht zu taskSchema passt, markiert TypeScript es direkt dort im Handler, bevor der Code je läuft. Das Output-Schema ist ein Vertrag über deinen eigenen Server-Code, nicht nur über den Client.

Den Router als Route Handler mounten

oRPC stellt einen RPC-Handler bereit, der schlichtes fetch Request/Response spricht, was direkt auf einen Next.js-16-Route-Handler abbildet. Mounte ihn auf einem Catch-all-Segment, damit jede Procedure im Router unter einem Präfix erreichbar ist.

// 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;

Jedes HTTP-Verb zeigt auf dieselbe handleRequest-Funktion, weil der RPC-Handler aus dem Request selbst herausfindet, welche Procedure aufzurufen ist. Du schreibst keine separate Datei pro Endpoint, und du mappst URLs nicht von Hand auf Funktionen. Eine neue Procedure zum router in lib/orpc/router.ts hinzuzufügen ist der ganze Diff.

Aus einer Server Component aufrufen

Innerhalb einer Server Component gibt es keinen Grund, für einen Call an deinen eigenen Server über HTTP hin und her zu gehen. Der serverseitige Client von oRPC (createRouterClient) ruft die Handler-Funktion der Procedure direkt auf, in-process, bei genau demselben getypten Interface wie der Netzwerk-Client.

// 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: {},
});

Der server-only-Import ist ein Schutzgeländer, es wirft einen Build-Error, falls diese Datei je versehentlich in Client-Code gezogen wird. Nutz es in einer Seite:

// 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>
  );
}

tasks ist Task[], den ganzen Weg von taskSchema inferiert. Kein manueller Return-Typ auf TasksPage, kein any, das sich aus einem ungetypten fetch-Call einschleicht.

Aus einer Client Component aufrufen

Der Browser kann deine Server-Funktionen nicht in-process aufrufen, also geht der clientseitige Client über die Leitung, über den RPC-Link, der auf die vorhin gemountete Route zeigt. Das Interface ist identisch mit dem serverseitigen Client, dieselben Methodennamen, dieselben Argument-Formen, dieselben inferierten Return-Typen.

// 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);

Nutz es in einem Formular. title ist ein schlichter String aus useState, und der Rückgabewert von orpc.tasks.create(...) ist eine vollständig getypte Task, keine Response, die du .json()-en und casten musst.

// 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>
  );
}

Wenn du versuchst, { title, done: false } in orpc.tasks.create zu übergeben, würde TypeScript es ablehnen, bevor du irgendetwas ausführst, weil createTaskInput nur title akzeptiert. Das ist das Input-Schema, das auf der Client-Seite seinen Job macht, nicht nur auf der Server-Seite.

Dem Compiler beim Abfangen einer Breaking Change zusehen

Das ist der Teil, den es sich lohnt, tatsächlich auszuprobieren. Öffne lib/schemas/task.ts und benenne done in completed um:

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

Speicher die Datei. NewTaskForm zeigt jetzt einen Compile-Error bei task.done: Property 'done' does not exist on type '{ id: string; title: string; completed: boolean }'. lib/db/tasks.ts fehlert auch, da createTask immer noch ein Objekt mit einem done-Key baut. Nichts läuft, bis jeder einzelne davon gefixt ist.

Das ist der ganze Pitch. Das Schema hat sich einmal geändert, in einer Datei, und TypeScript hat jede Stelle stromabwärts gefunden, die die alte Form annahm. Kein Grep nach .done über die Codebase, kein Runtime-Error Tage später, wenn ein echter Nutzer auf den veralteten Code-Pfad trifft.

Claude Code nutzen, um das zu verkabeln

Lass Claude Code eine neue Procedure hinzufügen, und es folgt dem Pattern, sobald deine CLAUDE.md die Konvention klar benennt:

## 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

Damit das steht, produziert "add a procedure to mark a task done" einen neuen Eintrag im selben Router-Objekt, ein Schema-Update, falls nötig, und einen Handler, der zum bestehenden Stil passt, statt einer frischen Ad-hoc-API-Route mit ihren eigenen handgeschriebenen Typen.

Das $29 Code Kit baut auf genau diesem Pattern auf. Es ist ein Gerüst auf Claude Code, kein separates Framework, und die API-Schicht, die es generiert, ist standardmäßig oRPC mit Zod, typsicher von deinen Datenbank-Queries bis zur Komponente, die die Daten rendert. Du brauchst weiterhin einen bezahlten Anthropic-Plan, um Claude Code selbst zu betreiben. Was das Gerüst hinzufügt, ist die Konvention schon verkabelt, sodass jedes neue Feature, das es baut, dieselbe Garantie bekommt, ohne dass du jedes Mal danach fragen musst.

Posted by @speedy_devv

Continue in Workflow

  • Agentic Commerce: Wie du eine App baust, für die KI-Agents bezahlen können
    Ein Guide in einfachem Deutsch zu Agentic Commerce im Jahr 2026: Was x402, ACP und das Machine Payments Protocol tun, plus eine Wochenend-Anleitung, um eine bezahlte API auszuliefern, von der KI-Agents kaufen können.
  • Claude Code Best Practices
    Fünf Gewohnheiten trennen Entwickler, die mit Claude Code liefern: PRDs, modulare CLAUDE.md-Regeln, Custom-Slash-Commands, /clear-Resets und eine System-Evolutions-Denkweise.
  • Claude Code Auto-Modus
    Ein zweites Sonnet-Modell prüft jeden Claude Code-Tool-Aufruf, bevor er ausgeführt wird. Was der Auto-Modus blockiert, was er erlaubt, und die Erlaubnisregeln, die er in deine Einstellungen schreibt.
  • Channels, Routines, Teleport, Dispatch
    Die vier Claude-Code-Features, die Anthropic im März und April 2026 ausgeliefert hat und die die CLI in eine ereignisgesteuerte Koordinationsschicht über Handy, Web und Desktop verwandeln.
  • 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.
  • Claude Code Changelog
    Release-Notizen für Claude Code von der v0.2-Beta bis März 2026. Bare-Modus, Channels-Berechtigungsweiterleitung, OAuth-Fixes und alle 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.
  • Prompt-Vorlagen, die Code liefern
    Zehn Prompt-Rezepte, die Code liefern: Full-Stack-Scaffolding, APIs, Schemas, Tests, Refactoring, Debugging, Reviews und CI. Jeweils mit den Fehlermodi, die es zu vermeiden gilt.

Sie möchten das Framework hinter diesen Projekten?

Holen Sie sich das Claude Code System, mit dem wir produktionsreife Software planen, bauen, testen und ausliefern.

Sehen Sie, was wir für Unternehmen bauen →
speedy_devvkoen_salo

Transaktions-E-Mails mit Claude Code einbauen (Resend + React Email)

So baust du Willkommens-, Beleg- und Passwort-Reset-E-Mails in einer Next.js-16-App mit Resend und React Email, wobei Claude Code die Templates und die Send-Logik schreibt.

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

Der Bug, den dieses Setup verhindert
oRPC und Zod installieren
Das Schema definieren
Eine oRPC-Procedure definieren
Den Router als Route Handler mounten
Aus einer Server Component aufrufen
Aus einer Client Component aufrufen
Dem Compiler beim Abfangen einer Breaking Change zusehen
Claude Code nutzen, um das zu verkabeln

Sie möchten das Framework hinter diesen Projekten?

Holen Sie sich das Claude Code System, mit dem wir produktionsreife Software planen, bauen, testen und ausliefern.

Sehen Sie, was wir für Unternehmen bauen →