Skip to content
tRPC
v11 · stable

Move Fast and Break Nothing.End-to-end typesafe APIs made easy.

Experience the full power of TypeScript inference to boost productivity for your full-stack application.

$ pnpm add @trpc/server @trpc/client

Supported by

const 
const trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>
trpc
=
createTRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>(opts: CreateTRPCClientOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>): TRPCClient<...>
createTRPCClient
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
>({
links: TRPCLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>[]
links
: [
httpBatchLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>(opts: HTTPBatchLinkOptions<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>): TRPCLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>>
httpBatchLink
({
url: string | URLurl: 'http://localhost:3000', }), ], }); const res = await
const trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>
trpc
.
greeting: {
    query: Resolver<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        errorShape: DefaultErrorShape;
        transformer: false;
    }>;
}
greeting
.
query: (input: {
    name: string;
}, opts?: TRPCProcedureOptions) => TRPCTaggedPromise<`Hello ${string}`, DefaultErrorShape>
query
({ name: stringname: 'John' });
const res: `Hello ${string}`

The client of the router below: the types come from the server, with nothing generated.

As used by

  • Netflix
  • Google
  • Cal.com
  • PayPal
  • Langfuse
  • Mistral
  • Documenso
  • Legora
  • Pleo
  • Ping.gg

How it works

It's quick and easy to get started with tRPC to build a typesafe API.

  1. Define your procedures

    Define procedures
    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
    ();
    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
    ;
    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;
    const
    const appRouter: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>
    appRouter
    =
    const router: RouterBuilder
    <{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>(_: {
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }) => BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>
    router
    ({
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>
    greeting
    : 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>.query<`Hello ${string}`>(resolver: ProcedureResolver<object, object, object, {
        name: string;
    }, UnsetMarker, `Hello ${string}`>): QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>
    query
    ((
    opts: ProcedureResolverOptions<object, object, object, {
        name: string;
    }>
    opts
    ) => {
    const { input } =
    opts: ProcedureResolverOptions<object, object, object, {
        name: string;
    }>
    opts
    ;
    const input: {
        name: string;
    }
    return `Hello ${
    const input: {
        name: string;
    }
    input
    .name: stringname}` as type const = `Hello ${string}`const;
    }), }); export type
    type AppRouter = Router<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>> & DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>
    AppRouter
    = typeof
    const appRouter: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>
    appRouter
    ;
  2. Create your HTTP server

    Adapters
    const { 
    const listen: {
        (port?: number, hostname?: string, backlog?: number, listeningListener?: () => void): Server<typeof IncomingMessage, typeof ServerResponse>;
        (port?: number, hostname?: string, listeningListener?: () => void): Server<typeof IncomingMessage, typeof ServerResponse>;
        (port?: number, backlog?: number, listeningListener?: () => void): Server<typeof IncomingMessage, typeof ServerResponse>;
        (port?: number, listeningListener?: () => void): Server<...>;
        (path: string, backlog?: number, listeningListener?: () => void): Server<...>;
        (path: string, listeningListener?: () => void): Server<...>;
        (options: ListenOptions, listeningListener?: () => void): Server<...>;
        (handle: any, backlog?: number, listeningListener?: () => void): Server<...>;
        (handle: any, listeningListener?: () => void): Server<...>;
    }
    listen
    } =
    createHTTPServer<BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{}>>>(opts: CreateHTTPHandlerOptions<BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{}>>>): Server<typeof IncomingMessage, typeof ServerResponse>
    createHTTPServer
    ({
    BaseHandlerOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, IncomingMessage>.router: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{}>>
    router
    :
    const appRouter: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{}>>
    appRouter
    ,
    }); // The API will now be listening on port 3000! const listen: (port?: number, hostname?: string, backlog?: number, listeningListener?: () => void) => Server<typeof IncomingMessage, typeof ServerResponse> (+8 overloads)listen(3000);
  3. Connect your client and start querying!

    The vanilla client
    const 
    const trpc: TRPCClient<BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>>
    trpc
    =
    createTRPCClient<BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>>(opts: CreateTRPCClientOptions<BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>>): TRPCClient<...>
    createTRPCClient
    <
    type AppRouter = Router<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>> & DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>
    AppRouter
    >({
    links: TRPCLink<BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>>[]
    links
    : [
    httpBatchLink<BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>>(opts: HTTPBatchLinkOptions<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }>): TRPCLink<BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<...>>>
    httpBatchLink
    ({
    url: string | URLurl: 'http://localhost:3000', }), ], }); const res = await
    const trpc: TRPCClient<BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        greeting: QueryProcedure<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>>
    trpc
    .
    greeting: {
        query: Resolver<{
            input: {
                name: string;
            };
            output: `Hello ${string}`;
            errorShape: DefaultErrorShape;
            transformer: false;
        }>;
    }
    greeting
    .
    query: (input: {
        name: string;
    }, opts?: TRPCProcedureOptions) => TRPCTaggedPromise<`Hello ${string}`, DefaultErrorShape>
    query
    ({ name: stringname: 'John' });
    const res: `Hello ${string}`

Features

  • Automatic typesafetyMade a server side change? TypeScript will warn you of errors on your client before you even save the file!
  • Snappy DXtRPC has no build or compile steps, meaning no code generation, runtime bloat or build step.
  • Framework agnosticCompatible with all JavaScript frameworks and runtimes. It's easy to add to your existing projects.
  • AutocompletionUsing tRPC is like using an SDK for your API's server code, giving you confidence in your endpoints.
  • Light bundle sizetRPC has zero dependencies and a tiny client-side footprint making it lightweight.
  • Batteries includedWe provide adapters for React, Next.js, Express, Fastify, AWS Lambda, Solid, Svelte, and more.

Sponsors

tRPC is MIT licensed and built in the open, kept going by the companies and people below. Thank you!

Become a sponsor