Claude Code और tRPC से type-safe API बनाएं
Claude Code और tRPC से type-safe API बनाएं: Next.js, Zod, permission, cache invalidation और review prompts.
tRPC और Claude Code साथ में क्यों उपयोगी हैं
tRPC में server-side TypeScript router ही client के लिए API contract बन जाता है। अलग OpenAPI file या हाथ से लिखी SDK रखने की जरूरत कम हो जाती है। React client AppRouter type import करता है और उसे input, output और error shape की type मदद मिलती है। सरल भाषा में, server और client एक ही TypeScript समझौते को पढ़ते हैं।
Claude Code का उपयोग सिर्फ boilerplate generate करने तक सीमित नहीं होना चाहिए। असली लाभ तब मिलता है जब यह repository की folder structure, authentication, data access, React Query pattern और naming rules पढ़कर उसी style में router, procedure, Zod validation, authorization और cache invalidation लिखता है। फिर भी type safety business rule की जगह नहीं लेती। कोई mutation compile हो सकती है, लेकिन अगर teamId check नहीं है तो वह दूसरे team का data बदल सकती है।
इस article में Next.js App Router के साथ छोटा project-management API बनाया गया है। Example copy करके समझने लायक रहे, इसलिए storage के लिए memory में Map रखा गया है। Production में इसे Prisma, Drizzle, Supabase या अपनी database layer से बदलें, और demo session को real authentication से replace करें।
उपयोग के अच्छे उदाहरण
tRPC उन projects में अच्छा काम करता है जहां server और front-end एक ही TypeScript codebase में साथ बदलते हैं। Public third-party API के लिए OpenAPI बेहतर हो सकता है, लेकिन admin dashboard, internal tools, forms और thin BFF के लिए tRPC बहुत practical है।
| Use case | tRPC का लाभ | Claude Code से क्या करवाएं | Risk |
|---|---|---|---|
| Admin CRUD | list, create, update, delete के input-output जुड़े रहते हैं | router, Zod schema, mutation, invalidation | UI में button छिपाकर server permission भूलना |
| Internal tool | workflow जल्दी बदला जा सकता है | existing model से procedure बनाना | context में बहुत dependency डालना |
| Form submit | email, length, enum runtime पर validate होते हैं | error message और double-submit guard | सिर्फ TypeScript पर भरोसा |
| Thin BFF | screen को जरूरी shape में data मिलता है | external API response map करना | cache refresh unclear रहना |
flowchart LR
UI["React component"]
Client["tRPC React client"]
Router["AppRouter"]
Procedure["protected/admin procedure"]
Zod["Zod validation"]
Store["DB or store"]
Review["Claude Code review"]
UI --> Client --> Router --> Procedure --> Zod --> Store
Router --> Review
Review --> Procedure
Install और file structure
सबसे पहले packages install करें। अगर project में React Query या Zod पहले से है, तो versions मिलाकर चलें।
npm install @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zod superjson
इस guide में यह structure माना गया है। Server code src/server में रहेगा, HTTP entry App Router में रहेगी, और React provider src/trpc में रहेगा।
src/
app/api/trpc/[trpc]/route.ts
app/projects/project-list.tsx
server/trpc.ts
server/routers/_app.ts
server/routers/project.ts
trpc/client.tsx
Claude Code को शुरुआत में boundary साफ बताएं: server files client components import न करें, client components store को direct access न करें, और shared types tRPC router से आएं, अलग DTO file से नहीं।
context और protected procedure
context हर request की जानकारी procedure तक पहुंचाता है। इसे छोटा रखें: session, team, role और database access काफी होता है। नीचे code local demo के लिए headers पढ़ता है।
// src/server/trpc.ts
import { initTRPC, TRPCError } from "@trpc/server";
import superjson from "superjson";
type Role = "admin" | "member";
type Session = { userId: string; teamId: string; role: Role };
export type Context = { session: Session | null };
export async function createContext({
headers,
}: {
headers: Headers;
}): Promise<Context> {
const roleHeader = headers.get("x-user-role");
const role: Role =
roleHeader === "admin"
? "admin"
: roleHeader === "member"
? "member"
: process.env.NODE_ENV === "production"
? "member"
: "admin";
return {
session: {
userId: headers.get("x-user-id") ?? "demo-user",
teamId: headers.get("x-team-id") ?? "demo-team",
role,
},
};
}
const t = initTRPC.context<Context>().create({ transformer: superjson });
export const createTRPCRouter = t.router;
export const publicProcedure = t.procedure;
const requireUser = t.middleware(({ ctx, next }) => {
if (!ctx.session) {
throw new TRPCError({ code: "UNAUTHORIZED", message: "Login is required." });
}
return next({ ctx: { session: ctx.session } });
});
export const protectedProcedure = t.procedure.use(requireUser);
export const adminProcedure = protectedProcedure.use(({ ctx, next }) => {
if (ctx.session.role !== "admin") {
throw new TRPCError({ code: "FORBIDDEN", message: "Admin role is required." });
}
return next();
});
protectedProcedure और adminProcedure को अलग रखने से review आसान होता है। Delete, invite या billing जैसी mutation अगर normal protected procedure पर है, तो Claude Code और reviewer तुरंत सवाल पूछ सकते हैं।
Zod validation वाला router
TypeScript build time पर मदद करता है, लेकिन browser से आए JSON को runtime में validate नहीं करता। Zod यही काम procedure boundary पर करता है।
// src/server/routers/project.ts
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
type ProjectStatus = "todo" | "doing" | "done";
type Project = {
id: string;
teamId: string;
title: string;
ownerEmail: string;
status: ProjectStatus;
createdAt: string;
};
const projects = new Map<string, Project>();
const projectStatus = z.enum(["todo", "doing", "done"]);
const listProjectsInput = z
.object({
status: projectStatus.optional(),
query: z.string().trim().max(60).optional(),
limit: z.number().int().min(1).max(50).default(20),
})
.default({ limit: 20 });
const createProjectInput = z.object({
title: z.string().trim().min(2).max(80),
ownerEmail: z.string().email(),
});
export const projectRouter = createTRPCRouter({
list: protectedProcedure.input(listProjectsInput).query(({ ctx, input }) => {
return [...projects.values()]
.filter((project) => project.teamId === ctx.session.teamId)
.filter((project) => !input.status || project.status === input.status)
.filter((project) => !input.query || project.title.includes(input.query))
.slice(0, input.limit);
}),
create: adminProcedure.input(createProjectInput).mutation(({ ctx, input }) => {
const project: Project = {
id: crypto.randomUUID(),
teamId: ctx.session.teamId,
title: input.title,
ownerEmail: input.ownerEmail,
status: "todo",
createdAt: new Date().toISOString(),
};
projects.set(project.id, project);
return project;
}),
updateStatus: protectedProcedure
.input(z.object({ id: z.string().uuid(), status: projectStatus }))
.mutation(({ ctx, input }) => {
const project = projects.get(input.id);
if (!project || project.teamId !== ctx.session.teamId) {
throw new TRPCError({ code: "NOT_FOUND" });
}
const nextProject = { ...project, status: input.status };
projects.set(input.id, nextProject);
return nextProject;
}),
});
teamId check बहुत जरूरी है। UUID valid होना permission नहीं है। Multi-tenant app में यह check हटते ही दूसरे team के data पर write हो सकता है।
App Router और React client
Router को compose करके Route Handler में जोड़ें।
// src/server/routers/_app.ts
import { createTRPCRouter } from "../trpc";
import { projectRouter } from "./project";
export const appRouter = createTRPCRouter({
project: projectRouter,
});
export type AppRouter = typeof appRouter;
// src/app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { appRouter } from "@/server/routers/_app";
import { createContext } from "@/server/trpc";
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
createContext: () => createContext({ headers: req.headers }),
});
export { handler as GET, handler as POST };
React side पर tRPC client और React Query provider बनाएं।
// src/trpc/client.tsx
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
import { useState, type ReactNode } from "react";
import superjson from "superjson";
import type { AppRouter } from "@/server/routers/_app";
export const trpc = createTRPCReact<AppRouter>();
export function TRPCProvider({ children }: { children: ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
transformer: superjson,
links: [httpBatchLink({ url: "/api/trpc" })],
}),
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</trpc.Provider>
);
}
// src/app/projects/project-list.tsx
"use client";
import { useState } from "react";
import { trpc } from "@/trpc/client";
export function ProjectList() {
const utils = trpc.useUtils();
const [title, setTitle] = useState("");
const projects = trpc.project.list.useQuery({ limit: 20 });
const createProject = trpc.project.create.useMutation({
onSuccess: async () => {
setTitle("");
await utils.project.list.invalidate();
},
});
return (
<form
onSubmit={(event) => {
event.preventDefault();
createProject.mutate({ title, ownerEmail: "owner@example.com" });
}}
>
<input value={title} onChange={(event) => setTitle(event.target.value)} />
<button type="submit">Add</button>
<pre>{JSON.stringify(projects.data, null, 2)}</pre>
</form>
);
}
Mutation success के बाद utils.project.list.invalidate() call करने से list fresh होती है। यह न हो तो data बना होगा, लेकिन screen पुरानी list दिखा सकती है।
Claude Code review prompt
इस Next.js App Router + tRPC implementation को review करें।
Check करें:
1. publicProcedure, protectedProcedure, adminProcedure सही जगह हैं या नहीं
2. हर external input पर Zod runtime validation है या नहीं
3. read/write से पहले teamId और userId boundary check है या नहीं
4. mutation के बाद affected queries invalidate होती हैं या नहीं
5. router domain के हिसाब से split हैं या नहीं
6. context में अनावश्यक dependency तो नहीं
7. client को secret fields return तो नहीं हो रहे
Severity, file, problem, fix और recommended tests table में दें।
इस prompt को CLAUDE.md या pull request template में रखा जा सकता है। Permission change में Claude Code से पहले diff और risk explanation मांगें, फिर human review के बाद apply करें।
आम गलतियां
पहली गलती context को बहुत बड़ा करना है। सब कुछ ctx में डालने से tests कठिन हो जाते हैं। दूसरी गलती authorization को सिर्फ UI में करना है; hidden button API को सुरक्षित नहीं करता। तीसरी गलती TypeScript पर भरोसा करके Zod हटाना है। Real requests runtime पर validate होने चाहिए। चौथी गलती router split न करना है। एक बड़ा file review को कमजोर करता है। पांचवीं गलती cache invalidation को बाद में सोचना है। एक mutation list, detail, count और dashboard को साथ बदल सकती है।
Official links और next step
Implementation से पहले tRPC के Routers, Procedures, और React Query integration पढ़ें। Runtime validation के लिए Zod docs और App Router endpoint के लिए Next.js Route Handlers देखें।
Input validation को और मजबूत करना हो तो Claude Code Zod validation guide पढ़ें। TypeScript design के लिए Claude Code TypeScript tips उपयोगी है। अगर आपकी team existing admin tool या internal app की tRPC architecture review करना चाहती है, तो Claude Code consultation और training में router design, permission review और prompts पर साथ काम किया जा सकता है।
असल में आजमाने पर
छोटे admin prototype में सबसे अच्छा परिणाम तब मिला जब CRUD से पहले protectedProcedure और adminProcedure बनाए गए। Claude Code आसानी से बता पाया कि कौन सी mutation admin-only होनी चाहिए। Zod schema को procedure के पास रखने से form field और API input साथ review हुए। दूसरी ओर, context में ज्यादा सुविधा जोड़ते ही tests भारी लगे, इसलिए final structure में context छोटा रखा गया और business logic अलग functions में गया।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code Obsidian to CLAUDE.md workflow: context बार-बार न समझाएं
Obsidian notes को CLAUDE.md operating notes में बदलकर Claude Code sessions को resume करना आसान बनाएं.
Claude Code Revenue CTA Routing: article से PDF, Gumroad और consultation तक
Reader intent के आधार पर free PDF, Gumroad products और consultation तक CTA route करने वाला workflow.
Claude Code टीम हैंडऑफ नियम: review proof, permissions, rollback और revenue path
Claude Code टीम काम के लिए evidence, permission rules, rollback, free PDF, Gumroad और consultation path वाला handoff.