Skip to main content
Version: 10.x

Quickstart

tip

We highly encourage you to check out the Example Apps to get a feel of tRPC and getting up & running as seamless as possible.

Installation

⚠️ Requirements: tRPC requires TypeScript > 4.1 as it relies on Template Literal Types.

For implementing tRPC endpoints and routers. Install in your server codebase.

npm install @trpc/server@next

For making typesafe API calls from your client. Install in your client codebase (@trpc/server is a peer dependency of @trpc/client).

npm install @trpc/client@next @trpc/server@next

For generating a powerful set of React hooks for querying your tRPC API. Powered by @tanstack/react-query.

npm install @trpc/react-query@next @tanstack/react-query

For a set of utilies for integrating tRPC with Next.js.

npm install @trpc/next@next

Installation Snippets

npm

bash
npm install @trpc/server@next @trpc/client@next @trpc/react-query@next @trpc/next@next @tanstack/react-query
bash
npm install @trpc/server@next @trpc/client@next @trpc/react-query@next @trpc/next@next @tanstack/react-query

yarn

bash
yarn add @trpc/server@next @trpc/client@next @trpc/react-query@next @trpc/next@next @tanstack/react-query
bash
yarn add @trpc/server@next @trpc/client@next @trpc/react-query@next @trpc/next@next @tanstack/react-query

pnpm

bash
pnpm add @trpc/server@next @trpc/client@next @trpc/react-query@next @trpc/next@next @tanstack/react-query
bash
pnpm add @trpc/server@next @trpc/client@next @trpc/react-query@next @trpc/next@next @tanstack/react-query

Defining a router

Let's walk through the steps of building a typesafe API with tRPC. To start, this API will only contain two endpoints:

ts
userById(id: string) => { id: string; name: string; }
userCreate(data: {name:string}) => { id: string; name: string; }
ts
userById(id: string) => { id: string; name: string; }
userCreate(data: {name:string}) => { id: string; name: string; }

Create a router instance

First we define a router somewhere in our server codebase:

server.ts
ts
import { initTRPC } from '@trpc/server';
 
const t = initTRPC.create();
 
const appRouter = t.router({});
 
// only export *type signature* of router!
// to avoid accidentally importing your API
// into client-side code
export type AppRouter = typeof appRouter;
server.ts
ts
import { initTRPC } from '@trpc/server';
 
const t = initTRPC.create();
 
const appRouter = t.router({});
 
// only export *type signature* of router!
// to avoid accidentally importing your API
// into client-side code
export type AppRouter = typeof appRouter;

Add a query procedure

Use t.procedure.query() to add a query procedure/endpoint to the router. Methods:

  • input: Optional. This should be a function that validates/casts the input of this procedure and either returns a strongly typed value (if valid) or throws an error (if invalid). Alternatively you can pass a Zod, Superstruct or Yup schema.
  • query: This is the actual implementation of the procedure (a "resolver"). It's a function with a single req argument. The validated input is passed into req.input and the context is in req.ctx (more about context later!)

The following would create a query procedure called userById that takes a single string argument and returns a user object:

server.ts
ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
const t = initTRPC.create();
 
interface User {
id: string;
name: string;
}
 
const userList: User[] = [
{
id: '1',
name: 'KATT',
},
];
 
const appRouter = t.router({
userById: t.procedure
.input((val: unknown) => {
if (typeof val === 'string') return val;
throw new Error(`Invalid input: ${typeof val}`);
})
.query((req) => {
const { input } = req;
const input: string
const user = userList.find((u) => u.id === input);
 
return user;
}),
});
 
export type AppRouter = typeof appRouter;
server.ts
ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
const t = initTRPC.create();
 
interface User {
id: string;
name: string;
}
 
const userList: User[] = [
{
id: '1',
name: 'KATT',
},
];
 
const appRouter = t.router({
userById: t.procedure
.input((val: unknown) => {
if (typeof val === 'string') return val;
throw new Error(`Invalid input: ${typeof val}`);
})
.query((req) => {
const { input } = req;
const input: string
const user = userList.find((u) => u.id === input);
 
return user;
}),
});
 
export type AppRouter = typeof appRouter;

Add a mutation procedure

Similarly to GraphQL, tRPC makes a distinction between query and mutation procedures. Let's add a userCreate mutation:

ts
userCreate(payload: {name: string}) => {id: string; name: string};
ts
userCreate(payload: {name: string}) => {id: string; name: string};
server.ts
ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
const t = initTRPC.create();
 
interface User {
id: string;
name: string;
}
 
const userList: User[] = [
{
id: '1',
name: 'KATT',
},
];
 
const appRouter = t.router({
userById: t.procedure
.input((val: unknown) => {
if (typeof val === 'string') return val;
throw new Error(`Invalid input: ${typeof val}`);
})
.query((req) => {
const input = req.input;
const input: string
const user = userList.find((it) => it.id === input);
 
return user;
const user: User | undefined
}),
userCreate: t.procedure
.input(z.object({ name: z.string() }))
.mutation((req) => {
const id = `${Math.random()}`;
const user: User = {
id,
name: req.input.name,
};
userList.push(user);
return user;
}),
});
 
export type AppRouter = typeof appRouter;
server.ts
ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
const t = initTRPC.create();
 
interface User {
id: string;
name: string;
}
 
const userList: User[] = [
{
id: '1',
name: 'KATT',
},
];
 
const appRouter = t.router({
userById: t.procedure
.input((val: unknown) => {
if (typeof val === 'string') return val;
throw new Error(`Invalid input: ${typeof val}`);
})
.query((req) => {
const input = req.input;
const input: string
const user = userList.find((it) => it.id === input);
 
return user;
const user: User | undefined
}),
userCreate: t.procedure
.input(z.object({ name: z.string() }))
.mutation((req) => {
const id = `${Math.random()}`;
const user: User = {
id,
name: req.input.name,
};
userList.push(user);
return user;
}),
});
 
export type AppRouter = typeof appRouter;

Using your new backend on the client

Setup the tRPC Client

client.ts
ts
// @filename: client.ts
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';
 
const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
}),
],
});
client.ts
ts
// @filename: client.ts
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';
 
const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
}),
],
});

Querying & mutating

client.ts
ts
// Inferred types
const user = await trpc.userById.query('1');
const user: User | undefined
 
const createdUser = await trpc.userCreate.mutate({ name: 'sachinraja' });
const createdUser: User
client.ts
ts
// Inferred types
const user = await trpc.userById.query('1');
const user: User | undefined
 
const createdUser = await trpc.userCreate.mutate({ name: 'sachinraja' });
const createdUser: User

Full autocompletion

client.ts
ts
// Full autocompletion on your routes
trpc.u;
      
client.ts
ts
// Full autocompletion on your routes
trpc.u;
      

Next steps

tRPC includes more sophisticated client-side tooling designed for React projects generally and Next.js specifically. Read the appropriate guide next: