adapter-express
Mount tRPC as Express middleware with createExpressMiddleware() from @trpc/server/adapters/express. Access Express req/res in createContext via CreateExpressContextOptions. Mount at a path prefix like app.use('/trpc', ...). Adapter options: onError, maxBatchSize, maxBodySize, middleware. Body parsers registered before tRPC (Express 4's express.json(), or express.raw()) break FormData and binary inputs, so scope them to non-tRPC routes.
Express Adapter
From https://trpc.io/docs/server/adapters/express
Example app
| Description | Links |
|---|---|
| Express server & procedure calls with Node.js. |
How to add tRPC to existing Express project
1. Install deps
yarn add @trpc/server zodZod isn't a required dependency, but it's used in the sample router below.
2. Create a tRPC router
Implement your tRPC router. A sample router is given below:
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
export const appRouter = t.router({
getUser: t.procedure.input(z.string()).query((opts) => {
opts.input; // string
return { id: opts.input, name: 'Bilbo' };
}),
createUser: t.procedure
.input(z.object({ name: z.string().min(5) }))
.mutation(async (opts) => {
// use your ORM of choice
return { id: '1', ...opts.input };
}),
});
// export type definition of API
export type AppRouter = typeof appRouter;If your router file starts getting too big, split your router into several subrouters each implemented in its own file. Then merge them into a single root appRouter.
3. Use the Express adapter
tRPC includes an adapter for Express out of the box. This adapter lets you convert your tRPC router into an Express middleware.
import { initTRPC } from '@trpc/server';
import * as trpcExpress from '@trpc/server/adapters/express';
import express from 'express';
// created for each request
const createContext = ({
req,
res,
}: trpcExpress.CreateExpressContextOptions) => ({}); // no context
type Context = Awaited<ReturnType<typeof createContext>>;
const t = initTRPC.context<Context>().create();
const appRouter = t.router({
// [...]
});
const app = express();
app.use(
'/trpc',
trpcExpress.createExpressMiddleware({
router: appRouter,
createContext,
}),
);
app.listen(4000);Your endpoints are now available via HTTP!
| Endpoint | HTTP URI |
|---|---|
| getUser | GET http://localhost:4000/trpc/getUser?input=INPUT where INPUT is a URI-encoded JSON string. |
| createUser | POST http://localhost:4000/trpc/createUser with req.body of type {name: string} |
createContext receives Express's req and res, so you can, for example, read req.headers.authorization to authorize the user. The middleware accepts the options every adapter takes, like onError and maxBatchSize, plus middleware and maxBodySize.
Body parsers
tRPC parses request bodies itself, so tRPC routes don't need express.json(). If a body parser runs before tRPC anyway, tRPC uses the req.body it parsed, which works for JSON. For FormData and binary inputs it depends on your Express version:
- Express 5: express.json() and express.urlencoded() leave other content types unread, so tRPC still reads them.
- Express 4: the parsers set req.body to {} on content types they skip, so tRPC receives {} instead of the form data or file.
- In both versions, a parser that reads the body you upload, like express.raw() for application/octet-stream, breaks binary inputs.
To be safe, register body parsers only on the routes that need them:
import * as trpcExpress from '@trpc/server/adapters/express';
import express from 'express';
import { appRouter } from './router';
const app = express();
app.use('/api', express.json()); // only for your own `/api/*` routes
app.post('/api/webhook', (req, res) => {
res.json({ received: req.body });
});
app.use('/trpc', trpcExpress.createExpressMiddleware({ router: appRouter }));
app.listen(4000);Common options
Every HTTP adapter accepts these options, next to its own (like endpoint for Fetch, or basePath for Standalone):
| Option | Description |
|---|---|
| router | Your app router. Required. |
| createContext | Creates the context for each request. It receives the adapter's own objects (e.g. req and res) plus info. |
| onError | Called with { error, type, path, input, ctx, req } for every error, e.g. to report it. See Error handling. |
| responseMeta | Returns { status, headers } for the response, e.g. for caching. |
| allowBatching | Set to false to reject batch requests. Defaults to true. |
| maxBatchSize | Rejects batch requests with more calls than this with a 400 Bad Request. Set maxItems on your batch link to the same or a lower value. |
| allowMethodOverride | Accepts queries sent as POST, for clients that set methodOverride: 'POST' on their link. Defaults to false. |
The Node.js adapters (Standalone, Express and Next.js) also accept:
- maxBodySize: the largest request body in bytes; larger ones are rejected with 413 PAYLOAD_TOO_LARGE.
- middleware: a connect-style (req, res, next) function that runs before tRPC, e.g. cors().
import { createHTTPServer } from '@trpc/server/adapters/standalone';
import { appRouter } from './router';
createHTTPServer({
router: appRouter,
createContext: () => ({}),
onError({ error, path }) {
console.error(`tRPC error on '${path}':`, error);
},
maxBatchSize: 10,
maxBodySize: 1_000_000, // 1 MB
}).listen(3000);From https://trpc.io/docs/server/adapters#common-options
Related skills
- server-setup (https://trpc.io/docs/server/overview): npx @tanstack/intent@latest load @trpc/server#server-setup