Logo

How tRPC Works for Type Safety in Next.js

šŸ”§ **Overview**: tRPC enables full-stack type safety in Next.js applications by letting you define your API procedures and types in one place and automatically inferring them on the client. No more manual API typings!

šŸ“‚ **Folder Structure** (Recommended for tRPC in Next.js):

1src/
2ā”œā”€ā”€ server/
3│   ā”œā”€ā”€ trpc/
4│   │   ā”œā”€ā”€ routers/
5│   │   │   ā”œā”€ā”€ example.ts
6│   │   │   └── _app.ts
7│   │   ā”œā”€ā”€ utils.ts
8│   │   └── context.ts
9│   └── index.ts
10ā”œā”€ā”€ pages/
11│   └── api/
12│       └── trpc/[trpc].ts
13└── utils/
14    └── trpc.ts

1ļøāƒ£ **Install tRPC dependencies:**

1npm install @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zod

2ļøāƒ£ **Server-Side Setup** (`src/server/trpc`):

1// utils.ts
2import { initTRPC } from "@trpc/server";
3
4const t = initTRPC.create();
5export const router = t.router;
6export const publicProcedure = t.procedure;
7
8// context.ts
9export const createContext = async () => ({});
10export type Context = Awaited<ReturnType<typeof createContext>>;
11
12// routers/example.ts
13import { publicProcedure, router } from "../utils";
14import { z } from "zod";
15
16export const exampleRouter = router({
17  hello: publicProcedure
18    .input(z.object({ name: z.string() }))
19    .query(({ input }) => {
20      return { greeting: `Hello, ${input.name}!` };
21    }),
22});
23
24// routers/_app.ts
25import { router } from "../utils";
26import { exampleRouter } from "./example";
27
28export const appRouter = router({
29  example: exampleRouter,
30});
31export type AppRouter = typeof appRouter;

3ļøāƒ£ **API Handler** (`pages/api/trpc/[trpc].ts`):

1import { createNextApiHandler } from "@trpc/server/adapters/next";
2import { appRouter } from "@/server/trpc/routers/_app";
3import { createContext } from "@/server/trpc/context";
4
5export default createNextApiHandler({
6  router: appRouter,
7  createContext,
8});

4ļøāƒ£ **Client-Side Setup** (`src/utils/trpc.ts`):

1import { createTRPCReact } from "@trpc/react-query";
2import type { AppRouter } from "@/server/trpc/routers/_app";
3
4export const trpc = createTRPCReact<AppRouter>();

5ļøāƒ£ **Using tRPC in a React Component**:

1import { trpc } from "@/utils/trpc";
2
3export default function HomePage() {
4  const helloQuery = trpc.example.hello.useQuery({ name: "Ravikant" });
5
6  if (helloQuery.isLoading) return <p>Loading...</p>;
7  if (helloQuery.error) return <p>Error: {helloQuery.error.message}</p>;
8
9  return <p>{helloQuery.data?.greeting}</p>;
10}

šŸŽÆ With this setup, your API input and output types are automatically inferred on the client from the server definitions — ensuring type safety across the stack without manual syncing.