tRPC lets you build fully type-safe APIs without schemas, code generation, or runtime validation overhead. Types flow from your backend to frontend automatically.
Installation
npm install @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zod
Server Setup
import { initTRPC } from "@trpc/server";
import { z } from "zod";
const t = initTRPC.create();
const router = t.router;
const publicProcedure = t.procedure;
const appRouter = router({
// Query — get data
hello: publicProcedure
.input(z.object({ name: z.string() }))
.query(({ input }) => {
return { greeting: `Hello, ${input.name}!` };
}),
// Query — list items
posts: publicProcedure
.input(z.object({ limit: z.number().default(10) }))
.query(async ({ input }) => {
return db.post.findMany({ take: input.limit, orderBy: { createdAt: "desc" } });
}),
// Mutation — create item
createPost: publicProcedure
.input(z.object({
title: z.string().min(1).max(200),
content: z.string().min(10)
}))
.mutation(async ({ input }) => {
return db.post.create({ data: input });
}),
// Mutation — delete
deletePost: publicProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input }) => {
await db.post.delete({ where: { id: input.id } });
return { success: true };
})
});
export type AppRouter = typeof appRouter;
Client (React)
import { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "./server";
const trpc = createTRPCReact<AppRouter>();
function Posts() {
const posts = trpc.posts.useQuery({ limit: 10 });
const createPost = trpc.createPost.useMutation({
onSuccess: () => posts.refetch()
});
if (posts.isLoading) return <p>Loading...</p>;
return (
<div>
<button onClick={() => createPost.mutate({ title: "New", content: "Hello world!" })}>
Create Post
</button>
{posts.data?.map(post => <div key={post.id}>{post.title}</div>)}
</div>
);
}
Middleware
const isAuthed = t.middleware(({ next, ctx }) => {
if (!ctx.user) throw new TRPCError({ code: "UNAUTHORIZED" });
return next({ ctx: { user: ctx.user } });
});
const protectedProcedure = t.procedure.use(isAuthed);
const appRouter = router({
secretData: protectedProcedure.query(({ ctx }) => {
return { secret: "Only for authenticated users", user: ctx.user.name };
})
});
Why tRPC?
- Zero code generation — types flow automatically
- Full autocomplete — Ctrl+Space shows all available procedures
- Runtime validation — Zod schemas validate at runtime
- Tiny bundle — no schema overhead on client
Need to extract or automate web content at scale? Check out my web scraping tools on Apify — no coding required. Or email me at spinov001@gmail.com for custom solutions.
Top comments (0)