---
name: adapter-express
description: "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."
requires:
  - server-setup
sources:
  - www/v11/docs/server/adapters/express.md
  - www/v11/docs/server/adapters.md
metadata:
  type: core
  library: trpc
  library_version: 11.23.0
---

<!-- Generated from www/v11/docs/server/adapters/express.md by www/build/skills.ts. Edit the docs instead. -->

# Express Adapter

From https://trpc.io/docs/server/adapters/express

## Example app

<table>
  <thead>
    <tr>
      <th>Description</th>
      <th>Links</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Express server &amp; procedure calls with Node.js.</td>
      <td>
        <ul>
          <li><a href="https://githubbox.com/trpc/trpc/tree/main/examples/express-server">CodeSandbox</a></li>
          <li><a href="https://github.com/trpc/trpc/tree/main/examples/express-server">Source</a></li>
        </ul>
      </td>
    </tr>
  </tbody>
</table>

## How to add tRPC to existing Express project

### 1. Install deps

```bash
yarn add @trpc/server zod
```

> [Zod](https://github.com/colinhacks/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:

```ts no-check title="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](https://trpc.io/docs/server/merging-routers) 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.

```ts no-check title="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!

| Endpoint     | HTTP URI                                                                                                   |
| ------------ | ---------------------------------------------------------------------------------------------------------- |
| `getUser`    | `GET http://localhost:4000/trpc/getUser?input=INPUT` <br/><br/>where `INPUT` is a URI-encoded JSON string. |
| `createUser` | `POST http://localhost:4000/trpc/createUser` <br/><br/>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](https://trpc.io/docs/server/authorization). The middleware accepts the [options every adapter takes](https://trpc.io/docs/server/adapters#common-options), 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](https://trpc.io/docs/server/non-json-content-types) 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:

```ts no-check title="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):

| Option                | Description                                                                                                                                         |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `router`              | Your app router. Required.                                                                                                                          |
| `createContext`       | Creates the [context](https://trpc.io/docs/server/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](https://trpc.io/docs/server/error-handling#handling-errors). |
| `responseMeta`        | Returns `{ status, headers }` for the response, e.g. for [caching](https://trpc.io/docs/server/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()`](https://trpc.io/docs/server/adapters/standalone#handling-cors--options).

```ts no-check title="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

## Related skills

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