If you have used tRPC, pyRPC will feel familiar. Same hooks, same kinds, same query keys. But the server is Python, not TypeScript. This post maps tRPC concepts to their pyRPC equivalents and shows what changes in your code.
Side by side
// tRPC (TypeScript server)
const appRouter = router({
greet: publicProcedure
.input(z.object({ name: z.string() }))
.query(({ input }) => `Hello, ${input.name}!`),
})
// pyRPC (Python server)
@rpc.query
def greet(name: str) -> str:
return f"Hello, {name}!"tRPC defines procedures with builders (router(), publicProcedure, .input(), .query()). pyRPC uses decorators. The Python function signature is the input schema. Pydantic validates the types.
What stays the same
Client setup. Almost identical:
// tRPC
import { createTRPCReact } from "@trpc/react-query"
const api = createTRPCReact<AppRouter>()
// pyRPC
import { createReactClient } from "@pyrpc/react"
const api = createReactClient<Types>({ baseUrl: "..." })Hook syntax. Same pattern:
// tRPC
const { data } = api.greet.useQuery({ name: "Ada" })
// pyRPC
const { data } = api.greet.useQuery({ name: "Ada" })Query keys. Both use predictable keys for cache invalidation.
Providers. Both wrap with a QueryClient provider.
What goes away
No Zod schemas. tRPC requires z.object() input validation on the server. pyRPC uses Python type hints. Pydantic validates at runtime. No schema duplication between server and client.
No appRouter type export. tRPC needs export type AppRouter = typeof appRouter. pyRPC generates Types from Python code. No manual type export.
No links chain. tRPC has httpBatchLink, httpBatchStreamLink, loggerLink. pyRPC uses one POST /rpc endpoint. No configuration.
No superjson. tRPC uses SuperJSON for date/Map/Set serialization. pyRPC uses standard JSON. If you need custom serialization, add it at the adapter level.
What gets easier
Server code. Three lines of Python vs a router builder with Zod schemas. The function signature is the contract.
No codegen for server types. tRPC infers types from the router at build time. pyRPC generates types at codegen time. The result is the same — fully typed client — but pyRPC works across language boundaries.
One install. pip install pyrpc-core[fastapi] gives you the runtime, CLI, and codegen. tRPC requires the server package, the client package, the adapter package, and Zod.
Framework adapters. tRPC has separate packages for React, Next.js, and vanilla. pyRPC has one per framework, each following the same pattern. Switching frameworks means changing one import.
Next.js migration
If you have a tRPC + Next.js App Router project, the migration path is:
- Replace
createTRPCReactwithcreateNextClient - Replace
trpc.prefix withapi. - Remove Zod schemas from the server — use Python type hints
- Replace
import type { AppRouter } fromwithimport type { Types } from "@pyrpc/types"
What you lose
- tRPC's batching (multiple calls in one HTTP request) — not yet in pyRPC
- tRPC's subscriptions — on pyRPC's roadmap
- The tRPC ecosystem (trpc-panel, trpc-openapi, etc.) — pyRPC has its own introspection and CLI
What you gain
- Python's type system (Pydantic, dataclasses, enums, unions)
- Any Python web framework (FastAPI, Flask, Django, Starlette)
- No build-time type inference — codegen works at dev time
- Cross-language by design — the same frontend works with any backend

pyRPC