tRPC is designed to provide end-to-end type safety by leveraging TypeScript's type inference, allowing developers to avoid manually writing types for API contracts. This approach is a core part of its developer experience and a key reason for its popularity in the TypeScript community. How tRPC Handles Types Type Inference by Default: tRPC automatically infers input and output types from your procedures and schemas (often defined with libraries like Zod), so you rarely need to explicitly annotate types in most of your codebase. No Extra Schemas: Unlike tools like OpenAPI or GraphQL, tRPC does not require separate schema definitions or code generation steps. Types flow directly from your backend procedures to your frontend consumers. Colocated Types: Types are often defined alongside the functions that use them, and can be exported for use elsewhere if needed, but explicit type definitions are minimized unless you want to share types across boundaries (e.g., separate repos).
1// Backend procedure with tRPC
2const userRouter = t.router({
3 getUser: t.procedure
4 .input(z.object({ id: z.string() }))
5 .query(async ({ input }) => {
6 // TypeScript knows input.id is a string
7 const user = await db.user.findUnique({ where: { id: input.id } });
8 // Return type is automatically inferred
9 return { id: user.id, name: user.name, email: user.email };
10 }),
11});
12
13// Frontend usage - no explicit types needed
14const UserProfile = ({ userId }: { userId: string }) => {
15 // TypeScript automatically knows the shape of data and error
16 const { data, isLoading, error } = trpc.user.getUser.useQuery({ id: userId });
17
18 if (isLoading) return <div>Loading...</div>;
19 if (error) return <div>Error: {error.message}</div>;
20
21 // TypeScript knows data has id, name, email properties
22 return (
23 <div>
24 <h1>{data.name}</h1>
25 <p>{data.email}</p>
26 </div>
27 );
28};
29
30// Compare with traditional REST API approach
31interface User {
32 id: string;
33 name: string;
34 email: string;
35}
36
37interface GetUserResponse {
38 user: User;
39}
40
41// Explicit types everywhere in traditional approach
42const fetchUser = async (id: string): Promise<User> => {
43 const response = await fetch(`/api/users/${id}`);
44 const data: GetUserResponse = await response.json();
45 return data.user;
46};
Created on 6/5/2025