tRPC
Shipkit includes a tRPC setup for type-safe API calls between client and server. It's wired up as a demo but ready for production use.
Structure
src/lib/trpc/
├── api/
│ ├── root.ts # App router (registers all sub-routers)
│ ├── trpc.ts # Context, middleware, procedure helpers
│ └── routers/
│ └── post.ts # Example router
├── react.tsx # React Query + tRPC client provider
├── query-client.ts # Shared QueryClient config
└── server.ts # Server-side caller
Adding a Router
- Create a file in
src/lib/trpc/api/routers/:
import { z } from "zod";
import { createTRPCRouter, publicProcedure, protectedProcedure } from "@/lib/trpc/api/trpc";
export const myRouter = createTRPCRouter({
list: publicProcedure.query(async ({ ctx }) => {
return ctx.db.query.myTable.findMany();
}),
create: protectedProcedure
.input(z.object({ name: z.string() }))
.mutation(async ({ ctx, input }) => {
// ctx.session.user is guaranteed here
await ctx.db.insert(myTable).values({ name: input.name });
}),
});
- Register it in
src/lib/trpc/api/root.ts:
import { myRouter } from "./routers/my-router";
export const appRouter = createTRPCRouter({
post: postRouter,
my: myRouter,
});
Client Usage
"use client";
import { api } from "@/lib/trpc/react";
export function MyComponent() {
const { data } = api.my.list.useQuery();
const create = api.my.create.useMutation();
return <button onClick={() => create.mutate({ name: "test" })}>Create</button>;
}
Server Usage
import { createCaller } from "@/lib/trpc/api/root";
const trpc = createCaller(createContext);
const items = await trpc.my.list();
Procedures
| Type | Import | Auth required |
|---|---|---|
publicProcedure | @/lib/trpc/api/trpc | No |
protectedProcedure | @/lib/trpc/api/trpc | Yes — ctx.session guaranteed |
Demo Route
The tRPC demo page lives at /trpc (src/app/(app)/(demo)/trpc/page.tsx).