Does Express Work With tRPC?
Express and tRPC work together seamlessly—tRPC provides the adapter pattern to mount its router directly onto Express.
Quick Facts
How Express Works With tRPC
tRPC ships with an official Express adapter that makes integration trivial. You create your tRPC router and procedures as you normally would, then use the `createExpressMiddleware` function to mount it as a single route handler on your Express app. This gives you all of Express's middleware ecosystem—logging, authentication, CORS, compression—while tRPC handles your type-safe RPC layer on top.
The developer experience is excellent: you define your API procedures once in TypeScript, and the generated client types are automatically inferred from your server code. Requests go through standard HTTP (POST by default), so all existing Express middleware, rate limiting, and deployment patterns work unchanged. The single middleware mounts at a specific path (typically `/trpc`), and all your procedures tunnel through it, keeping your route definitions clean and centralized.
This is particularly well-suited for developers who want the flexibility of Express but are tired of duplicating type definitions between client and server. There are no architectural conflicts—Express handles routing and middleware, tRPC handles the RPC protocol and type safety.
Best Use Cases
Quick Setup
npm install express @trpc/server @trpc/client zodimport express from 'express';
import { initTRPC } from '@trpc/server';
import { createExpressMiddleware } from '@trpc/server/adapters/express';
import { z } from 'zod';
const t = initTRPC.create();
const router = t.router({
hello: t.procedure
.input(z.object({ name: z.string() }))
.query(({ input }) => ({
message: `Hello, ${input.name}!`,
})),
addPost: t.procedure
.input(z.object({ title: z.string() }))
.mutation(({ input }) => ({
id: 1,
title: input.title,
})),
});
export type AppRouter = typeof router;
const app = express();
app.use(
'/trpc',
createExpressMiddleware({
router,
createContext: () => ({}),
})
);
app.listen(3000, () => console.log('Server running on :3000'));Known Issues & Gotchas
File upload handling requires additional setup since tRPC doesn't natively handle multipart/form-data
Fix: Use `express.json()` and `express.urlencoded()` middleware before tRPC, or handle file uploads on separate Express routes outside tRPC
CORS headers must be set on the Express app itself, not within tRPC
Fix: Apply `cors()` middleware to your Express app before mounting the tRPC middleware
tRPC batch requests can bypass standard Express rate-limiting if not configured at the middleware level
Fix: Apply rate limiting middleware to the entire Express app or specifically to the tRPC route
Alternatives
- •Fastify + tRPC: Similar zero-codegen RPC model with better async/await support and performance
- •Next.js API Routes + tRPC: Built-in framework with file-based routing and built-in tRPC support via `next-trpc`
- •Hono + tRPC: Lightweight edge-first alternative with smaller bundle size, good for serverless
Resources
Related Compatibility Guides
Explore more compatibility guides