Skip to content

@trpc/server

The tRPC server library

adapter-express

191 linesSource

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

shell
yarn add @trpc/server zod

Zod 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:

server.ts
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.

server.ts
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!

EndpointHTTP URI
getUserGET http://localhost:4000/trpc/getUser?input=INPUT

where INPUT is a URI-encoded JSON string.
createUserPOST 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:

server.ts
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):

OptionDescription
routerYour app router. Required.
createContextCreates the context for each request. It receives the adapter's own objects (e.g. req and res) plus info.
onErrorCalled with { error, type, path, input, ctx, req } for every error, e.g. to report it. See Error handling.
responseMetaReturns { status, headers } for the response, e.g. for caching.
allowBatchingSet to false to reject batch requests. Defaults to true.
maxBatchSizeRejects 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.
allowMethodOverrideAccepts 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().
server.ts
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

  • server-setup (https://trpc.io/docs/server/overview): npx @tanstack/intent@latest load @trpc/server#server-setup