Client-Server Type Inference
Get fully typed clients without code generation.
Client-Server Type Inference is a free TypeScript Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Sharing the Router Type
The client gains type safety by importing the server router type. This is a type-only import: nothing from the server runs in the client; only the static contract crosses the boundary.
import type { AppRouter } from '../server/router';
// type-only import - erased at build timeCreating a Typed Client
Use createTRPCClient (or the proxy variant) parameterized by AppRouter. The client object then mirrors the server router shape, with full autocomplete for every procedure.
import { createTRPCClient, httpBatchLink } from '@trpc/client';
const client = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:3000' })],
});Calling Procedures
Calls mirror the router tree. A nested procedure user.byId is reached as client.user.byId, and you call .query or .mutate on it. Inputs and outputs are typed from the server.
const user = await client.user.byId.query({ id: 1 });
// user is typed exactly as the server resolver returnsInferred Inputs
The client knows each procedure input from the server schema. Passing the wrong shape is a compile error before you ever run the app.
await client.user.byId.query({ id: 1 }); // ok
// await client.user.byId.query({ id: 'x' }); // error: id must be number
// await client.user.byId.query({}); // error: id is requiredInferred Outputs
Return types flow back too. The resolved value of a query is exactly what the server resolver returns, so downstream code is fully checked.
const u = await client.user.byId.query({ id: 1 });
const name: string = u.name; // ok if server returns name: stringMutations on the Client
Mutations are called with .mutate. Same inference applies: input validated by the schema type, output typed from the resolver.
const created = await client.user.create.mutate({ name: 'Bea' });
// created is typed from the create resolver's returnThe Inference Flow
Trace how a type travels: a zod schema on the server infers the input type, the resolver return infers the output type, typeof appRouter captures both into AppRouter, and the client maps that type onto callable methods. One change ripples through automatically.
// schema -> input type
// resolver -> output type
// appRouter -> AppRouter (both captured)
// client<AppRouter> -> typed callsInferring Helper Types
tRPC exposes helpers to extract input and output types for reuse, such as in React components. inferRouterInputs and inferRouterOutputs give you those maps from AppRouter.
import type { inferRouterOutputs } from '@trpc/server';
type Outputs = inferRouterOutputs<AppRouter>;
type User = Outputs['user']['byId'];No Drift Guarantee
Because the client is typed from the live router type, server and client cannot drift. Rename a procedure or change an input and the client fails to compile until you update the call. The compiler is your contract test.
// Server renames user.byId -> user.find
// client.user.byId becomes a type error immediatelyBatching and Links
The links array configures transport. httpBatchLink batches multiple calls made in the same tick into one HTTP request, reducing round trips. This is a runtime optimization that does not affect the types.
links: [httpBatchLink({ url: '/trpc' })]
// Several .query calls in one tick -> one requestEnd-to-End in Practice
The result: you call client.user.byId.query(...) like a local function, with autocomplete on inputs, checked arguments, and a typed result, all derived from server code with no generated files. That is end-to-end type safety delivered by inference.
const u = await client.user.byId.query({ id: 7 });
console.log(u.name); // fully typed, no codegenQuick Check
Test your understanding of client-server inference.
Recap
You consumed a tRPC API with full type safety.
- Import
AppRouteras a type-only import. createTRPCClient<AppRouter>mirrors the router tree.- Inputs and outputs are inferred from server schemas and resolvers.
- Client and server cannot drift; the compiler enforces the contract.
Next: context and middleware for auth.
Frequently asked questions
Is the “Client-Server Type Inference” lesson free?
Yes — the full text of “Client-Server Type Inference” is free to read here on the web, and the TypeScript Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO.
What will I learn in “Client-Server Type Inference”?
Get fully typed clients without code generation. You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start TypeScript Academy?
No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Client-Server Type Inference” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this TypeScript Academy lesson?
Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- The tRPC Architecture
- Defining Routers and Procedures
- Client-Server Type Inference
- Middleware and Context