On This Page
tRPC
tRPC (opens in a new window) is a TypeScript library that provides full-stack end-to-end type safety for applications, using inference to keep your types in sync from client to server.
You can see the complete example in the companion repo (opens in a new window) to this guide.
Installation
This guide assumes tRPC v11.x and Zod 4.x.
First, let's install the tRPC client and server packages, as well as Zod (opens in a new window); a TypeScript schema validation library.
npm i @trpc/client @trpc/server zod
yarn add @trpc/client @trpc/server zod
pnpm add @trpc/client @trpc/server zod
Demo
For this example, we assume an existing data source. The demonstration repo uses SQLite with Node's node:sqlite client sourced from a JSON file, but you can use any data source you want.
There are three key files:
- Greenwood Dynamic API Route - Single endpoint for handling all tRPC requests
- Client - For making API requests to the backend
- Router - Source of truth for the API contract
Router
Let's start with the router, which will be used by both the client and API endpoint. For this demo, we are configuring two methods for making requests:
- List all planets
- Get a single planet
import { initTRPC } from '@trpc/server';
// replace with your data source of choice
import { db } from "../db/db.ts";
import { z } from "zod";
const t = initTRPC.create();
const publicProcedure = t.procedure;
const router = t.router;
const planetsRouter = router({
listPlanets: publicProcedure.query(() => db.getPlanets()),
getPlanetById: publicProcedure
.input(z.number())
.query((opts) => db.getPlanetById(opts.input))
});
export const appRouter = router({
planets: planetsRouter,
});
export type AppRouter = typeof appRouter;
Client
Next, let's create our client, which we can then call from the frontend:
import { createTRPCProxyClient, httpBatchLink, loggerLink } from '@trpc/client';
import type { AppRouter } from './router.ts';
const url: URL = new URL(`${window.location.origin}/api/rpc`);
const client = createTRPCProxyClient<AppRouter>({
links: [loggerLink(), httpBatchLink({ url })],
});
export { client };
Endpoint
The last step is to create our dynamic API endpoint using tRPC's fetch adapter (opens in a new window), and wire up the router to it:
You can define the RPC endpoint and filename to be anything you want, it just has to be a dynamic route and match the url you define in the client. So any of the following would also work, for example:
- src/pages/api/rpc/[rpc].ts
- src/pages/api/rpc/[url].ts
- src/pages/api/rpc/[handler].ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '../../../trpc/router.ts';
export async function handler(request: Request): Promise<Response> {
const response = await fetchRequestHandler({
endpoint: '/api/rpc',
req: request,
router: appRouter,
});
return response;
}
Usage
Putting it all together we can now make calls with the client from anywhere within our application 🚀
import { client } from './trpc/client.ts'
const planets = await client.planets.listPlanets.query();
planets.forEach((planet) => console.log({ planet }));