tRPC developers build and maintain the end-to-end type-safe API infrastructure that eliminates the REST/GraphQL schema layer between TypeScript server and client — defining procedures on the server that clients call as type-safe functions with full autocomplete, where TypeScript errors appear immediately when the server changes a response shape or input validation schema rather than at runtime in production. At remote-first technology companies, they serve as the full-stack TypeScript engineers who own the API contract between frontend and backend — designing the router hierarchy that organizes procedures by domain, implementing middleware for authentication and context injection, and integrating tRPC with Next.js App Router, Express, or Fastify backends while the client consumes procedures through the TanStack Query adapter that handles caching, loading states, and optimistic updates.
What tRPC developers do
tRPC developers define procedures — writing query procedures (publicProcedure.query(() => db.users.findMany())) for read operations and mutation procedures (publicProcedure.input(z.object({ name: z.string() })).mutation(({ input }) => db.users.create({ data: input }))) for write operations, with Zod schema validation on inputs that provides both runtime validation and TypeScript type inference; organize routers — creating domain-specific routers (userRouter, postRouter) with router({ users: userRouter, posts: postRouter }) that nest into a root AppRouter type exported for client consumption; implement context — defining the createContext function that extracts request data (headers, cookies, session) and injects it into every procedure's ctx argument, making authentication state and database connections available without passing them explicitly; implement middleware — writing const protectedProcedure = publicProcedure.use(async ({ ctx, next }) => { if (!ctx.session) throw new TRPCError({ code: 'UNAUTHORIZED' }); return next({ ctx: { ...ctx, session: ctx.session } }) }) for authentication guards reused across procedures; configure server adapters — integrating tRPC with Next.js App Router via fetchRequestHandler, with Express via createExpressMiddleware, with Fastify via fastifyTRPCPlugin, or with standalone HTTP via createHTTPServer; configure the client — creating the tRPC client with createTRPCClient<AppRouter>({ links: [httpBatchLink({ url: '/api/trpc' })] }) and the React Query integration with createTRPCReact<AppRouter>() for hook-based data fetching; implement batching — using httpBatchLink so multiple tRPC procedure calls in the same render cycle are batched into a single HTTP request, reducing waterfall round trips; implement subscriptions — using subscription procedures with async generators for real-time data streams over WebSocket or Server-Sent Events with wsLink or splitLink; implement optimistic updates — using TanStack Query's useMutation with onMutate and onError callbacks for immediate UI updates that roll back on procedure failure; configure error handling — using TRPCError with standard codes (UNAUTHORIZED, NOT_FOUND, BAD_REQUEST, INTERNAL_SERVER_ERROR) that map to HTTP status codes, and implementing the error formatter for consistent client-facing error shapes; implement input validation — using Zod schemas with z.string().email(), z.number().min(0), and z.object() for structured input validation that gives TypeScript type inference on both server and client; and integrate with ORMs — using Prisma, Drizzle, or Kysely directly in procedure resolvers with the database client injected through context.
Key skills for tRPC developers
- Procedures: query; mutation; subscription; input (Zod); output; publicProcedure; protectedProcedure
- Routers: router(); mergeRouters; AppRouter type; router nesting; procedure namespacing
- Context: createContext; request context; session injection; DB client injection; inferContext
- Middleware: use(); TRPCError; UNAUTHORIZED; procedure.use chaining; ctx extension
- Server adapters: fetchRequestHandler (Next.js); createExpressMiddleware; fastifyTRPCPlugin
- Client: createTRPCClient; createTRPCReact; httpBatchLink; splitLink; wsLink
- TanStack Query: useQuery; useMutation; useUtils (cache invalidation); optimistic updates
- Zod: z.object; z.string; z.number; z.enum; z.infer; input validation; output inference
- Error handling: TRPCError; error codes; error formatter; client-side error types
- Next.js: App Router + tRPC; pages/api/trpc route; RSC + tRPC; server-side caller
Salary expectations for remote tRPC developers
Remote tRPC developers earn $95,000–$158,000 total compensation. Base salaries range from $80,000–$130,000, with equity at technology companies where API type safety, full-stack TypeScript development velocity, and the elimination of client-server contract drift directly affect feature development speed, production bug rates, and the confidence with which full-stack teams ship API changes. tRPC developers with deep Next.js App Router integration expertise combining tRPC with React Server Components, complex middleware chain design for multi-tenant applications with fine-grained authorization, subscription procedure implementations for real-time features using WebSocket links, and demonstrated developer experience improvements where tRPC replaced REST APIs and eliminated client-server type drift bugs command the strongest premiums. Those with tRPC combined with Prisma or Drizzle expertise for the complete full-stack TypeScript data layer earn toward the top of the range.
Career progression for tRPC developers
The path from tRPC developer leads to senior full-stack engineer (broader scope across the complete React and Node.js stack with API design, database modeling, and deployment), TypeScript platform engineer (owning the type-safe development infrastructure across the full application stack from database schema to UI), or technical lead (setting the API architecture standards and full-stack TypeScript conventions for multi-engineer product teams). Some tRPC developers specialize into the T3 Stack ecosystem — tRPC, TypeScript, Tailwind, and Prisma — becoming proficient in the opinionated full-stack architecture that create-t3-app bootstraps, and extending it for production scale. Others transition into API design engineering with a focus on GraphQL as an alternative type-safe API layer, applying their tRPC experience with typed procedure definitions to compare it with GraphQL's schema-first approach. tRPC developers who contribute to the open-source ecosystem often work on tRPC adapters, client integrations, or the tRPC core — the relatively small codebase makes contribution accessible for engineers who want to participate in the TypeScript full-stack ecosystem.
Remote work considerations for tRPC developers
Building tRPC applications for distributed engineering teams requires procedure organization standards, context injection documentation, and middleware chain conventions that allow distributed engineers to add new procedures, extend router domains, and implement authorization without creating procedure name conflicts, bypassing authentication middleware, or exposing database clients through context in ways that break procedure isolation. tRPC developers at remote companies document the procedure namespace convention — that procedure names should be organized as domain.action (users.list, users.create, posts.getById) and router merging should maintain these namespaces — because distributed engineers adding procedures to the wrong router create confusing API surfaces and break the client's autocomplete navigation; enforce middleware ordering — documenting that protectedProcedure must be used for any procedure that accesses user data or modifies resources, and that middleware chains should be composed as base procedure extensions (protectedProcedure.use(rateLimiter)) rather than per-procedure middleware additions — because distributed engineers implementing new features sometimes use publicProcedure for convenience and inadvertently expose authenticated operations; document the TanStack Query cache invalidation pattern — showing how utils.users.list.invalidate() after a mutation triggers a refetch of the users list, and when to prefer optimistic updates versus invalidation — because distributed engineers who don't understand the cache lifecycle implement mutations that don't update the UI until page reload; and standardize Zod schema colocation — keeping procedure input schemas co-located with the procedure definition (not in separate schema files) for easy discovery during code review of API changes.
Top industries hiring remote tRPC developers
- Next.js full-stack companies built on the T3 Stack where tRPC provides the type-safe API layer between Next.js API routes and React Query-powered frontend components — with create-t3-app generating the complete tRPC + Prisma + Tailwind + Auth.js stack for new projects
- TypeScript-first startup teams where the full-stack type safety tRPC provides — catching API contract breaks at TypeScript compile time rather than in production — enables small teams to move fast without dedicated API documentation or client code generation
- Monorepo organizations with shared types where tRPC's AppRouter type export enables the frontend package to import the type definition from the backend package and maintain end-to-end type safety across package boundaries without a code generation step
- Real-time application companies where tRPC's subscription procedures with WebSocket transport provide type-safe real-time data streams with the same procedure definition and type inference model as standard query and mutation procedures
- Internal tooling and admin dashboard companies where tRPC's ability to call procedures directly (without HTTP) in server-side contexts enables Next.js server components and API routes to call tRPC procedures as regular TypeScript functions for server-side data fetching with full type safety
Interview preparation for tRPC developer roles
Expect router setup questions: write the tRPC router structure for a blog application with posts (list, getById, create, update, delete) and comments (list, create) — what the router definitions, procedure types, and AppRouter export look like. Input validation questions ask how you'd validate a createPost input that requires a title (string, min 3 chars), body (string, max 5000 chars), and optional tags (array of strings) — what the Zod schema definition and the input() call look like. Middleware questions ask how you'd implement an authentication guard that throws UNAUTHORIZED when no session exists and makes the session available in ctx — what the protectedProcedure middleware definition looks like. Context questions ask how you'd make the Prisma client and the authenticated session available to all procedures — what the createContext function and the inferred context type look like. Client questions ask how you'd implement a React component that fetches a list of posts and shows a loading state, error state, and the rendered list — what the createTRPCReact hook setup and the useQuery call look like. Cache invalidation questions ask how you'd ensure the posts list refetches after a new post is created — what the useMutation onSuccess callback and utils.posts.list.invalidate() call look like. Be ready to compare tRPC with REST APIs and GraphQL — the specific trade-offs, when tRPC's monorepo type sharing is impractical (public APIs, multi-language backends), and when GraphQL's schema introspection and ecosystem are preferable.
Tools and technologies for tRPC developers
Core: @trpc/server; @trpc/client; @trpc/react-query; @trpc/next; tRPC v11. Server: router(); procedure; query; mutation; subscription; publicProcedure; mergeRouters; initTRPC. Adapters: @trpc/server/adapters/fetch (Next.js App Router); @trpc/server/adapters/express; @trpc/server/adapters/fastify; @trpc/server/adapters/standalone. Client: createTRPCClient; httpBatchLink; httpLink; wsLink; splitLink; loggerLink. React integration: createTRPCReact; useQuery; useMutation; useInfiniteQuery; useSubscription; useUtils (cache). Context: createContext function; inferAsyncReturnType; TRPCContext; session injection. Validation: Zod (standard); Yup (alternative); Valibot (lightweight alternative); custom validators. Error handling: TRPCError; PARSE_ERROR; BAD_REQUEST; UNAUTHORIZED; FORBIDDEN; NOT_FOUND; INTERNAL_SERVER_ERROR; error formatter. Next.js: pages/api/trpc/[trpc].ts; app/api/trpc/[trpc]/route.ts; createCallerFactory (server-side calls); experimental_createTRPCNextAppDirClient. TypeScript: AppRouter type export; inferRouterInputs; inferRouterOutputs; RouterInputs; RouterOutputs. Testing: createCallerFactory for unit testing procedures without HTTP; @trpc/server testing utilities. T3 Stack: create-t3-app; Prisma + tRPC; Auth.js + tRPC; Tailwind + tRPC; Next.js App Router. Alternatives: GraphQL (schema introspection, multi-language, ecosystem); REST + OpenAPI (public APIs, language-agnostic); Hono RPC (lightweight, edge-compatible); ts-rest (REST with contract types).
Global remote opportunities for tRPC developers
tRPC developer expertise is in strong and growing demand globally, with tRPC's emergence as the standard type-safe API layer for TypeScript full-stack applications — with over 3 million weekly npm downloads, inclusion in create-t3-app's widely-adopted full-stack starter (used by thousands of teams), and adoption in production at companies including Cal.com, Ping.gg, and Linen — creating consistent demand for full-stack TypeScript engineers who understand both tRPC's procedure model and the Next.js and TanStack Query ecosystem that tRPC integrates with. US-based tRPC developers are in demand at TypeScript-first SaaS startups adopting the T3 Stack, full-stack product teams where API contract drift is a recurring source of production bugs, and internal tooling organizations building admin applications where the monorepo type sharing tRPC enables is especially valuable. EMEA-based tRPC developers are well-positioned given tRPC's strong European open-source community — the library was created by KATT (Alex Johansson), a European developer, and has significant European contributor and user communities. tRPC's continued development with v11's improved streaming support, enhanced middleware types, and Next.js App Router integration ensures sustained engineering demand as TypeScript full-stack development continues to grow.
Frequently asked questions
How does tRPC achieve end-to-end type safety without code generation and what are the TypeScript mechanics behind it? tRPC achieves type safety through TypeScript's type inference system rather than a code generation step — the server defines procedures with Zod-validated inputs and typed return values, and the client imports the AppRouter type (not the runtime code) to get full type information about all available procedures. Server type export: export type AppRouter = typeof appRouter exports only the TypeScript type of the router object — no runtime code, no schemas, just the type shape. Client type consumption: const trpc = createTRPCReact<AppRouter>() creates a client where every method is typed based on the AppRouter type — calling trpc.users.list.useQuery() is type-safe because TypeScript knows users.list is a query procedure with its specific return type. Input type inference: when a procedure has .input(z.object({ id: z.string() })), the client's mutation or query call requires { id: string } — TypeScript shows a compile error if the client passes the wrong type. Return type inference: the procedure's resolver return type (inferred from the Prisma query, manual return type annotation, or output schema) becomes the return type of useQuery's data — no manual typing of API responses. Monorepo usage: the AppRouter type is typically exported from the server package and imported by the client package — TypeScript's path resolution handles the cross-package import without any build step or code generation.
How does tRPC's batching work and how do engineers configure links for different transport requirements? tRPC links are middleware in the client request pipeline — they transform, forward, or batch requests before sending them to the server. httpBatchLink: when multiple tRPC procedures are called in the same synchronous render cycle, httpBatchLink collects them into a batch and sends a single HTTP request to /api/trpc/users.list,users.getById?batch=1 with the procedure inputs as query parameters or request body — the server processes both procedures and returns combined results. Batching timing: React renders are batched by React 18's automatic batching — procedures called in event handlers, useEffect, and Suspense boundaries within the same render cycle get collected. Batch size: configure maxURLLength in httpBatchLink to split large batches that exceed URL limits. splitLink: routes requests to different transports based on operation type — splitLink({ condition: (op) => op.type === 'subscription', true: wsLink(...), false: httpBatchLink(...) }) sends subscriptions over WebSocket and queries/mutations over HTTP. loggerLink: adds request and response logging for development — combine it as the first link in the array. httpLink: the non-batching alternative to httpBatchLink for use cases where batching causes problems (streaming responses, abort signal requirements). Custom links: implement the Link interface for custom transport, request transformation, or analytics — link = (opts) => ({ handle: (op, next) => { console.log(op); return next(op) } }).
How does tRPC integrate with Next.js App Router and what is the difference between server and client procedure calls? tRPC integrates with Next.js App Router through two distinct patterns — server-side direct calls using createCallerFactory and client-side HTTP calls through the React Query adapter. Server-side caller: const createCaller = createCallerFactory(appRouter); const caller = createCaller(context) creates a caller that invokes procedures as TypeScript functions without HTTP — in Next.js React Server Components, you call const posts = await caller.posts.list() directly, and the procedure executes in the same Node.js process without an HTTP request. Client-side adapter: in Client Components (marked "use client"), import the tRPC React hooks and call trpc.posts.list.useQuery() — this makes an HTTP request to the tRPC API route and uses TanStack Query for caching and subscription. Route handler: create app/api/trpc/[trpc]/route.ts with const handler = (req: Request) => fetchRequestHandler({ endpoint: '/api/trpc', req, router: appRouter, createContext }) exported as GET and POST to handle client-side requests. Context in App Router: the createContext function receives the NextRequest and extracts the session from cookies or headers — const session = await getServerSession(authOptions) — making auth state available to all procedures. Data sharing between RSC and client: fetch data server-side in RSC using the direct caller for initial page load performance, then use client-side useQuery hooks for interactive updates — tRPC's output types ensure both paths return identically typed data.