Skip to content
tRPC

Quickstart

Learn how to quickly get started and setup tRPC

On this page

Installation

tRPC is split between several packages, so you can install only what you need. Make sure to install the packages you want in the proper sections of your codebase. For this quickstart guide we'll keep it simple and use the vanilla client only. For framework guides, check out usage with React and usage with Next.js.

Start off by installing the @trpc/server and @trpc/client packages:

pnpm add @trpc/server @trpc/client
npm install @trpc/server @trpc/client
yarn add @trpc/server @trpc/client
bun add @trpc/server @trpc/client

Your first tRPC API

Let's walk through the steps of building a typesafe API with tRPC. To start, this API will contain three endpoints with these TypeScript signatures:

type User = { id: string; name: string; };

userList: () => User[];
userById: (id: string) => User;
userCreate: (data: { name: string }) => User;

Here's the file structure we'll be building. We recommend separating tRPC initialization, router definition, and server setup into distinct files to prevent cyclic dependencies:

.
├── server/
│   ├── trpc.ts        # tRPC instantiation & setup
│   ├── appRouter.ts   # Your API logic and type export
│   └── index.ts       # HTTP server
└── client/
    └── index.ts       # tRPC client

1. Create a router instance

First, let's initialize the tRPC backend. It's good convention to do this in a separate file and export reusable helper functions instead of the entire tRPC object.

server/trpc.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';

/**
 * Initialization of tRPC backend
 * Should be done only once per backend!
 */
const 
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC.
TRPCBuilder<object, object>.create<RuntimeConfigOptions<object, object>>(opts?: RuntimeConfigOptions<object, object> | undefined): TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
/** * Export reusable router and procedure helpers * that can be used throughout the router */ export const
const router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
=
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
;
export const const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure =
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure;

Next, we'll initialize our main router instance, commonly referred to as appRouter, to which we'll later add procedures. Lastly, we need to export the type of the router which we'll later use on the client side.

server/appRouter.ts
import { 
const router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
} from './trpc';
export const
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
=
router<{}>(_: {}): BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
router
({
// ... }); export type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
;

2. Add a query procedure

Use publicProcedure.query() to add a query procedure to the router.

The following creates a query procedure called userList that returns a list of users:

server/appRouter.ts
import { const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure, 
const router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
} from './trpc';
export const
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
router<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}): BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
userList: QueryProcedure<{
    input: void;
    output: User[];
    meta: object;
    errorShape: DefaultErrorShape;
}>
userList
: const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure
.
ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<User[]>(resolver: ProcedureResolver<object, object, object, UnsetMarker, UnsetMarker, User[]>): QueryProcedure<{
    input: void;
    output: User[];
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(async () => {
const const users: User[]users:
type User = {
    id: string;
    name: string;
}
User
[] = [{ id: stringid: '1', name: stringname: 'Katt' }];
return const users: User[]users; }), }); export type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
;

3. Using input parser to validate procedure inputs

To implement the userById procedure, we need to accept input from the client. tRPC lets you define input parsers to validate and parse the input. You can define your own input parser or use a validation library of your choice, like zod, yup, or superstruct.

You define your input parser on publicProcedure.input(), which can then be accessed on the resolver function as shown below:

The input parser should be a function that validates and casts the input of this procedure. It should return a strongly typed value when the input is valid or throw an error if the input is invalid.


server/appRouter.ts
import { const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure, 
const router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
} from './trpc';
export const
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
router<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}): BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
// ...
userById: QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
userById
: const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure
// The input is unknown at this time. A client could have sent // us anything so we won't assume a certain data type. .ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<(val: unknown) => string>(schema: (val: unknown) => string): ProcedureBuilder<object, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>input((val: unknownval: unknown) => { // If the value is of type string, return it. // It will now be inferred as a string. if (typeof val: unknownval === 'string') return val: stringval; // Uh oh, looks like that input wasn't a string. // We will throw an error instead of running the procedure. throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
(`Invalid input: ${typeof val: unknownval}`);
}) .
ProcedureBuilder<object, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<User>(resolver: ProcedureResolver<object, object, object, string, UnsetMarker, User>): QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(async (opts: ProcedureResolverOptions<object, object, object, string>opts) => {
const { input } = opts: ProcedureResolverOptions<object, object, object, string>opts;
const input: string
const const user: Useruser:
type User = {
    id: string;
    name: string;
}
User
= { id: stringid: const input: stringinput, name: stringname: 'Katt' };
return const user: Useruser; }), }); export type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
;

The input parser can be any ZodType, e.g. z.string() or z.object({}).


server/appRouter.ts
import { const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure, 
const router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
} from './trpc';
import { import zz } from 'zod'; export const
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
router<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}): BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
// ...
userById: QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
userById
: const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure
.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodString>(schema: z.ZodString): ProcedureBuilder<object, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>input(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()) .
ProcedureBuilder<object, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<User>(resolver: ProcedureResolver<object, object, object, string, UnsetMarker, User>): QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(async (opts: ProcedureResolverOptions<object, object, object, string>opts) => {
const { input } = opts: ProcedureResolverOptions<object, object, object, string>opts;
const input: string
const const user: Useruser:
type User = {
    id: string;
    name: string;
}
User
= { id: stringid: const input: stringinput, name: stringname: 'Katt' };
return const user: Useruser; }), }); export type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
;

The input parser can be any YupSchema, e.g. yup.string() or yup.object({}).


server/appRouter.ts
import { const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure, 
const router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
} from './trpc';
import * as import yupyup from 'yup'; export const
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
router<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}): BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
// ...
userById: QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
userById
: const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure
.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<yup.StringSchema<string, yup.AnyObject, undefined, "">>(schema: yup.StringSchema<string, yup.AnyObject, undefined, "">): ProcedureBuilder<object, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>input(import yupyup.
function string(): yup.StringSchema (+1 overload)
export string
string
().StringSchema<string | undefined, AnyObject, undefined, "">.required(msg?: yup.Message): yup.StringSchema<string, yup.AnyObject, undefined, "">required())
.
ProcedureBuilder<object, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<User>(resolver: ProcedureResolver<object, object, object, string, UnsetMarker, User>): QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(async (opts: ProcedureResolverOptions<object, object, object, string>opts) => {
const { input } = opts: ProcedureResolverOptions<object, object, object, string>opts;
const input: string
const const user: Useruser:
type User = {
    id: string;
    name: string;
}
User
= { id: stringid: const input: stringinput, name: stringname: 'Katt' };
return const user: Useruser; }), }); export type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
;

The input parser can be any Valibot schema, e.g. v.string() or v.object({}).


server/appRouter.ts
import { const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure, 
const router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
} from './trpc';
import * as import vv from 'valibot'; export const
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
router<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}): BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
// ...
userById: QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
userById
: const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure
.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<v.StringSchema<undefined>>(schema: v.StringSchema<undefined>): ProcedureBuilder<object, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>input(import vv.
function string(): v.StringSchema<undefined> (+1 overload)
export string
string
())
.
ProcedureBuilder<object, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<User>(resolver: ProcedureResolver<object, object, object, string, UnsetMarker, User>): QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(async (opts: ProcedureResolverOptions<object, object, object, string>opts) => {
const { input } = opts: ProcedureResolverOptions<object, object, object, string>opts;
const input: string
const const user: Useruser:
type User = {
    id: string;
    name: string;
}
User
= { id: stringid: const input: stringinput, name: stringname: 'Katt' };
return const user: Useruser; }), }); export type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
;

4. Adding a mutation procedure

Similar to GraphQL, tRPC makes a distinction between Query and Mutation procedures.

The distinction between a Query and a Mutation is primarily semantic. Queries use HTTP GET and are intended for read operations, while Mutations use HTTP POST and are intended for operations that cause side effects.

Let's add a userCreate mutation by adding it as a new property on our router object:

server/appRouter.ts
import { const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure, 
const router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
} from './trpc';
export const
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
router<{
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}): BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
// ...
userCreate: MutationProcedure<{
    input: {
        name: string;
    };
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
userCreate
: const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure
.
ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
    name: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    name: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<object, object, object, {
    name: string;
}, {
    name: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input
(import zz.
function object<{
    name: z.ZodString;
}>(shape?: {
    name: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    name: z.ZodString;
}, z.core.$strip>
object
({ name: z.ZodStringname: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.
ProcedureBuilder<object, object, object, { name: string; }, { name: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.mutation<User>(resolver: ProcedureResolver<object, object, object, {
    name: string;
}, UnsetMarker, User>): MutationProcedure<{
    input: {
        name: string;
    };
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
mutation
(async (
opts: ProcedureResolverOptions<object, object, object, {
    name: string;
}>
opts
) => {
const { input } =
opts: ProcedureResolverOptions<object, object, object, {
    name: string;
}>
opts
;
const input: {
    name: string;
}
// Create the user in your DB const const user: Useruser:
type User = {
    id: string;
    name: string;
}
User
= { id: stringid: '1', ...
const input: {
    name: string;
}
input
};
return const user: Useruser; }), }); export type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
;

Serving the API

Now that we have defined our router, we can serve it. tRPC has first-class adapters for many popular web servers. To keep it simple, we'll use the standalone Node.js adapter here.

server/index.ts
import { function createHTTPServer<TRouter extends AnyRouter>(opts: CreateHTTPHandlerOptions<TRouter>): http.Server<any, any>createHTTPServer } from '@trpc/server/adapters/standalone';
import { 
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
} from './appRouter';
const const server: http.Server<any, any>server =
createHTTPServer<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>(opts: CreateHTTPHandlerOptions<...>): http.Server<...>
createHTTPServer
({
BaseHandlerOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{ userList: QueryProcedure<{ input: void; output: User[]; meta: object; errorShape: DefaultErrorShape; }>; userById: QueryProcedure<...>; userCreate: MutationProcedure<...>; }>>, http.IncomingMessage>.router: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
:
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
,
}); const server: http.Server<any, any>server.listen(3000);
See the full backend code
server/trpc.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';

const 
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC.
TRPCBuilder<object, object>.create<RuntimeConfigOptions<object, object>>(opts?: RuntimeConfigOptions<object, object> | undefined): TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
export const
const router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
=
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
;
export const const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure =
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure;

server/appRouter.ts
import { import zz } from "zod";
import { const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure, 
const router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
} from "./trpc";
type
type User = {
    id: string;
    name: string;
}
User
= { id: stringid: string; name: stringname: string };
export const
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
router<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}): BuiltRouter<...>
router
({
userList: QueryProcedure<{
    input: void;
    output: User[];
    meta: object;
    errorShape: DefaultErrorShape;
}>
userList
: const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure
.
ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<User[]>(resolver: ProcedureResolver<object, object, object, UnsetMarker, UnsetMarker, User[]>): QueryProcedure<{
    input: void;
    output: User[];
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(async () => {
const const users: User[]users:
type User = {
    id: string;
    name: string;
}
User
[] = [{ id: stringid: '1', name: stringname: 'Katt' }];
return const users: User[]users; }),
userById: QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
userById
: const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure
.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodString>(schema: z.ZodString): ProcedureBuilder<object, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>input(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()) .
ProcedureBuilder<object, object, object, string, string, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<User>(resolver: ProcedureResolver<object, object, object, string, UnsetMarker, User>): QueryProcedure<{
    input: string;
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(async (opts: ProcedureResolverOptions<object, object, object, string>opts) => {
const { const input: stringinput } = opts: ProcedureResolverOptions<object, object, object, string>opts; const const user: Useruser:
type User = {
    id: string;
    name: string;
}
User
= { id: stringid: const input: stringinput, name: stringname: 'Katt' };
return const user: Useruser; }),
userCreate: MutationProcedure<{
    input: {
        name: string;
    };
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
userCreate
: const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure
.
ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
    name: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    name: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<object, object, object, {
    name: string;
}, {
    name: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input
(import zz.
function object<{
    name: z.ZodString;
}>(shape?: {
    name: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    name: z.ZodString;
}, z.core.$strip>
object
({ name: z.ZodStringname: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.
ProcedureBuilder<object, object, object, { name: string; }, { name: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.mutation<User>(resolver: ProcedureResolver<object, object, object, {
    name: string;
}, UnsetMarker, User>): MutationProcedure<{
    input: {
        name: string;
    };
    output: User;
    meta: object;
    errorShape: DefaultErrorShape;
}>
mutation
(async (
opts: ProcedureResolverOptions<object, object, object, {
    name: string;
}>
opts
) => {
const {
const input: {
    name: string;
}
input
} =
opts: ProcedureResolverOptions<object, object, object, {
    name: string;
}>
opts
;
const const user: Useruser:
type User = {
    id: string;
    name: string;
}
User
= { id: stringid: '1', ...
const input: {
    name: string;
}
input
};
return const user: Useruser; }), }); export type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<...>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
;

server/index.ts
import { function createHTTPServer<TRouter extends AnyRouter>(opts: CreateHTTPHandlerOptions<TRouter>): http.Server<any, any>createHTTPServer } from "@trpc/server/adapters/standalone";
import { 
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
} from "./appRouter";
const const server: http.Server<any, any>server =
createHTTPServer<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>(opts: CreateHTTPHandlerOptions<...>): http.Server<...>
createHTTPServer
({
BaseHandlerOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{ userList: QueryProcedure<{ input: void; output: User[]; meta: object; errorShape: DefaultErrorShape; }>; userById: QueryProcedure<...>; userCreate: MutationProcedure<...>; }>>, http.IncomingMessage>.router: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
:
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
,
}); const server: http.Server<any, any>server.listen(3000);

Using your new backend on the client

Let's now move to the client-side code and embrace the power of end-to-end typesafety. When we import the AppRouter type for the client to use, we have achieved full typesafety for our system without leaking any implementation details to the client.

1. Setup the tRPC Client

client/index.ts
import { function createTRPCClient<TRouter extends AnyRouter>(opts: CreateTRPCClientOptions<TRouter>): TRPCClient<TRouter>createTRPCClient, function httpBatchLink<TRouter extends AnyRouter>(opts: HTTPBatchLinkOptions<TRouter["_def"]["_config"]["$types"]>): TRPCLink<TRouter>httpBatchLink } from '@trpc/client';
import type { 
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<...>
AppRouter
} from './appRouter';
// 👆 **type-only** imports are stripped at build time // Pass AppRouter as a type parameter. 👇 This lets `trpc` know // what procedures are available on the server and their input/output types. const
const trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>
trpc
=
createTRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>(opts: CreateTRPCClientOptions<...>): TRPCClient<...>
createTRPCClient
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<...>
AppRouter
>({
links: TRPCLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>[]
links
: [
httpBatchLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>(opts: HTTPBatchLinkOptions<...>): TRPCLink<...>
httpBatchLink
({
url: string | URLurl: 'http://localhost:3000', }), ], });

Links in tRPC are similar to links in GraphQL, they let us control the data flow to the server. In the example above, we use the httpBatchLink, which automatically batches up multiple calls into a single HTTP request. For more in-depth usage of links, see the links documentation.

2. Type Inference & Autocomplete

You now have access to your API procedures on the trpc object. Try it out!

client/index.ts
// Inferred types
const user = await 
const trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>
trpc
.
userById: {
    query: Resolver<{
        input: string;
        output: User;
        errorShape: DefaultErrorShape;
        transformer: false;
    }>;
}
userById
.query: (input: string, opts?: TRPCProcedureOptions) => TRPCTaggedPromise<User, DefaultErrorShape>query('1');
const user: User
const createdUser = await
const trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>
trpc
.
userCreate: {
    mutate: Resolver<{
        input: {
            name: string;
        };
        output: User;
        errorShape: DefaultErrorShape;
        transformer: false;
    }>;
}
userCreate
.
mutate: (input: {
    name: string;
}, opts?: TRPCProcedureOptions) => TRPCTaggedPromise<User, DefaultErrorShape>
mutate
({ name: stringname: 'Katt' });
const createdUser: User

You can also use your autocomplete to explore the API on your client

client/index.ts
const trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    userList: QueryProcedure<{
        input: void;
        output: User[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userById: QueryProcedure<{
        input: string;
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    userCreate: MutationProcedure<{
        input: {
            name: string;
        };
        output: User;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>
trpc
.u
  • userById
  • userCreate
  • userList
;
Property 'u' does not exist on type 'TRPCClient<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{ userList: QueryProcedure<{ input: void; output: User[]; meta: object; errorShape: DefaultErrorShape; }>; userById: QueryProcedure<...>; userCreate: MutationProcedure<...>; }>>>'.

Next steps

What's next?Description
Example AppsExplore tRPC in your chosen framework
TanStack React QueryRecommended React integration via @trpc/tanstack-react-query
Next.jsUsage with Next.js
Server AdaptersExpress, Fastify, and more
TransformersUse superjson to retain complex types like Date