---
name: server-setup
description: Set up a tRPC server. Initialize tRPC once with initTRPC.context<Context>().create(), define routers with t.router() and procedures with .query()/.mutation(), build reusable base procedures such as publicProcedure and authedProcedure, type and create the request context with createContext() (inner/outer context for tests and server-side calls), export the AppRouter type, nest child routers or merge them with t.mergeRouters(), lazy-load routers with lazy().
sources:
  - www/v11/docs/server/overview.md
  - www/v11/docs/server/routers.md
  - www/v11/docs/server/procedures.md
  - www/v11/docs/server/context.md
  - www/v11/docs/server/merging-routers.md
  - www/v11/docs/server/metadata.md
metadata:
  type: core
  library: trpc
  library_version: 11.25.0
---

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

# Backend Usage

From https://trpc.io/docs/server/overview

This section covers everything you need to set up and configure your tRPC backend. You'll learn how to define [routers](https://trpc.io/docs/server/routers) and [procedures](https://trpc.io/docs/server/procedures), set up [context](https://trpc.io/docs/server/context), add [input validation](https://trpc.io/docs/server/validators), and use [middlewares](https://trpc.io/docs/server/middlewares) to extend your API.

Once your API is defined, you can host it using one of the available [adapters](https://trpc.io/docs/server/adapters) for platforms like Express, Fastify, Next.js, AWS Lambda, and more.

## Initialize tRPC

You should initialize tRPC **exactly once** per application. Multiple instances of tRPC will cause issues: for example, `t.mergeRouters()` throws if the routers come from different instances that each set a [transformer](https://trpc.io/docs/server/data-transformers) or [error formatter](https://trpc.io/docs/server/error-formatting). Create `t` in one file and import your procedures and `router` from it everywhere.

```ts no-check title="server/trpc.ts"
import { initTRPC } from '@trpc/server';

// You can use any variable name you like.
// We use t to keep things simple.
const t = initTRPC.create();

export const router = t.router;
export const publicProcedure = t.procedure;
```

You'll notice we are exporting certain methods of the `t` variable here rather than `t` itself. This is to establish a certain set of procedures that we will use idiomatically in our codebase.

From https://trpc.io/docs/server/routers#initialize-trpc

## Defining a router

Next, let's define a router with a procedure to use in our application. We have now created an API "endpoint".

In order for these endpoints to be exposed to the frontend, your [Adapter](https://trpc.io/docs/server/adapters) should be configured with your `appRouter` instance.

```ts no-check title="server/_app.ts"
import { publicProcedure, router } from './trpc';

const appRouter = router({
  greeting: publicProcedure.query(() => 'hello tRPC v11!'),
});

// Export only the type of a router!
// This prevents us from importing server code on the client.
export type AppRouter = typeof appRouter;
```

On the client, import it with `import type { AppRouter } from '...'` and never import the `appRouter` value: a type-only import is always erased, so your server code can't end up in the client bundle.

Procedures can't be named `then`, `call` or `apply`: `router()` throws if you use these names.

From https://trpc.io/docs/server/routers#defining-a-router

## Defining an inline sub-router

When you define an inline sub-router, you can represent your router as a plain object.

In the below example, `nested1` and `nested2` are equal:

```ts no-check title="server/_app.ts"
import * as trpc from '@trpc/server';
import { publicProcedure, router } from './trpc';

const appRouter = router({
  // Using the router() method
  nested1: router({
    proc: publicProcedure.query(() => '...'),
  }),
  // Using an inline sub-router
  nested2: {
    proc: publicProcedure.query(() => '...'),
  },
});
```

From https://trpc.io/docs/server/routers#inline-sub-router

## Writing procedures

The `t` object you create during tRPC setup returns an initial `t.procedure` which all other procedures are built on:

```ts no-check
import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.context<{ signGuestBook: () => Promise<void> }>().create();

export const router = t.router;
export const publicProcedure = t.procedure;

const appRouter = router({
  // Queries are the best place to fetch data
  hello: publicProcedure.query(() => {
    return {
      message: 'hello world',
    };
  }),

  // Mutations are the best place to do things like updating a database
  goodbye: publicProcedure.mutation(async (opts) => {
    await opts.ctx.signGuestBook();

    return {
      message: 'goodbye!',
    };
  }),
});
```

From https://trpc.io/docs/server/procedures#writing-procedures

## Reusable "Base Procedures"

As a general pattern we recommend you rename and export `t.procedure` as `publicProcedure`, which then makes room for you to create other named procedures for specific use cases and export those too. This pattern is called "base procedures" and is a key pattern for code and behaviour re-use in tRPC; every application is likely to need it.

In the below code, we're using reusable base procedures to build common use-cases for our app - we're making a reusable base procedures for logged in users (`authedProcedure`) & another base procedure that takes an `organizationId` and validates that a user is part of that organization.

> This is a simplified example; in practice you may want to use some combination of [Headers](https://trpc.io/docs/client/headers), [Context](https://trpc.io/docs/server/context), [Middleware](https://trpc.io/docs/server/middlewares), and [Metadata](https://trpc.io/docs/server/metadata), to [authenticate](https://en.wikipedia.org/wiki/Authentication) and [authorize](https://trpc.io/docs/server/authorization) your users.

```ts no-check
import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';

type Organization = {
  id: string;
  name: string;
};
type Membership = {
  role: 'ADMIN' | 'MEMBER';
  Organization: Organization;
};
type User = {
  id: string;
  memberships: Membership[];
};
type Context = {
  /**
   * User is nullable
   */
  user: User | null;
};

const t = initTRPC.context<Context>().create();

export const publicProcedure = t.procedure;

// procedure that asserts that the user is logged in
export const authedProcedure = t.procedure.use(async function isAuthed(opts) {
  const { ctx } = opts;
  // `ctx.user` is nullable
  if (!ctx.user) {
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }

  return opts.next({
    ctx: {
      // ✅ user value is known to be non-null now
      user: ctx.user,
    },
  });
});

// procedure that asserts a user is a member of a specific organization
export const organizationProcedure = authedProcedure
  .input(z.object({ organizationId: z.string() }))
  .use(function isMemberOfOrganization(opts) {
    const membership = opts.ctx.user.memberships.find(
      (m) => m.Organization.id === opts.input.organizationId,
    );
    if (!membership) {
      throw new TRPCError({
        code: 'FORBIDDEN',
      });
    }
    return opts.next({
      ctx: {
        Organization: membership.Organization,
      },
    });
  });

export const appRouter = t.router({
  whoami: authedProcedure.query(async (opts) => {
    // user is non-nullable here
    const { ctx } = opts;
    return ctx.user;
  }),
  addMember: organizationProcedure
    .input(
      z.object({
        email: z.string().email(),
      }),
    )
    .mutation((opts) => {
      // ctx contains the non-nullable user & the organization being queried
      const { ctx } = opts;

      // input includes the validated email of the user being invited & the validated organizationId
      const { input } = opts;

      return '...';
    }),
});
```

From https://trpc.io/docs/server/procedures#reusable-base-procedures

## Defining the context type

When initializing tRPC using `initTRPC`, you should pipe `.context<TContext>()` to the `initTRPC` builder function before calling `.create()`. The type `TContext` can either be inferred from a function's return type or be explicitly defined.

This will make sure your context is properly typed in your procedures and middlewares.

```ts no-check
import { initTRPC } from '@trpc/server';
import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';

export const createContext = async (opts: CreateHTTPContextOptions) => {
  // Example: extract a session token from the request headers
  const token = opts.req.headers['authorization'];

  return {
    token,
  };
};

export type Context = Awaited<ReturnType<typeof createContext>>;
const t = initTRPC.context<Context>().create();

t.procedure.use((opts) => {
  opts.ctx;

  return opts.next();
});
```

From https://trpc.io/docs/server/context#defining-the-context-type

## Creating the context

The `createContext()` function must be passed to the handler mounting your appRouter. The handler may use HTTP or a [server-side call](https://trpc.io/docs/server/server-side-calls).

`createContext()` is called once per request, so all procedures within a single batched request share the same context.

```ts no-check
// 1. HTTP request
import { createHTTPHandler } from '@trpc/server/adapters/standalone';
import { createContext } from './context';
import { appRouter } from './router';

const handler = createHTTPHandler({
  router: appRouter,
  createContext,
});
```

```ts no-check
// 2. Server-side call
import { createContext } from './context';
import { createCaller } from './router';

const caller = createCaller(await createContext());
```

```ts no-check
// 3. Server-side helpers (Next.js-specific, see /docs/client/nextjs/pages-router/server-side-helpers)
import { createServerSideHelpers } from '@trpc/react-query/server';
import { createContext } from './context';
import { appRouter } from './router';

const helpers = createServerSideHelpers({
  router: appRouter,
  ctx: await createContext(),
});
```

From https://trpc.io/docs/server/context#creating-the-context

## Inner and outer context

In some scenarios it could make sense to split up your context into "inner" and "outer" functions.

**Inner context** is where you define context which doesn’t depend on the request, e.g. your database connection. You can use this function for integration testing or [server-side calls](https://trpc.io/docs/server/server-side-calls), where you don’t have a request object. Whatever is defined here will **always** be available in your procedures.

> [!NOTE] Tradeoff for large clients in `createContextInner`
> Putting a database client such as `prisma` on `createContextInner` is convenient and common, but large generated clients (like Prisma) can increase type-checking overhead because they become part of your context type across procedures.
>
> If that overhead becomes noticeable, an alternative is to keep context smaller and import the client directly at call sites where needed.


**Outer context** is where you define context which depends on the request, e.g. for the user's session. Whatever is defined here is only available for procedures that are called via HTTP.

### Example for inner & outer context

```ts no-check
import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
import { getSessionFromCookie, type Session } from './auth';
import { db } from './db';

/**
 * Defines your inner context shape.
 * Add fields here that the inner context brings.
 */
interface CreateInnerContextOptions {
  session: Session | null;
}

/**
 * Inner context. Will always be available in your procedures, in contrast to the outer context.
 *
 * Also useful for:
 * - testing, so you don't have to mock `req`/`res`
 * - server-side calls where we don't have `req`/`res`
 *
 * @see https://trpc.io/docs/v11/context#inner-and-outer-context
 */
export async function createContextInner(opts?: CreateInnerContextOptions) {
  return {
    db,
    session: opts?.session,
  };
}

/**
 * Outer context. Used in the routers and will e.g. bring `req` & `res` to the context as "not `undefined`".
 *
 * @see https://trpc.io/docs/v11/context#inner-and-outer-context
 */
export async function createContext(opts: CreateHTTPContextOptions) {
  const session = getSessionFromCookie(opts.req);

  const contextInner = await createContextInner({ session });

  return {
    ...contextInner,
    req: opts.req,
    res: opts.res,
  };
}

export type Context = Awaited<ReturnType<typeof createContextInner>>;

// The usage in your router is the same as the example above.
```

It is important to infer your `Context` from the **inner** context, as only what is defined there is really always available in your procedures.

If you don't want to check `req` or `res` for `undefined` in your procedures all the time, you could build a small reusable procedure for that:

```ts no-check
export const apiProcedure = publicProcedure.use((opts) => {
  if (!opts.ctx.req || !opts.ctx.res) {
    throw new Error('You are missing `req` or `res` in your call.');
  }
  return opts.next({
    ctx: {
      // We overwrite the context with the truthy `req` & `res`, which will also overwrite the types used in your procedure.
      req: opts.ctx.req,
      res: opts.ctx.res,
    },
  });
});
```

From https://trpc.io/docs/server/context#inner-and-outer-context

## Merging with child routers

```ts no-check title="routers/user.ts"
import { router, publicProcedure } from '../trpc';
import { z } from 'zod';

export const userRouter = router({
  list: publicProcedure.query(() => {
    // [..]
    return [];
  }),
});
```

```ts no-check title="routers/post.ts"
import { router, publicProcedure } from '../trpc';
import { z } from 'zod';

export const postRouter = router({
  create: publicProcedure
    .input(
      z.object({
        title: z.string(),
      }),
    )
    .mutation((opts) => {
      const { input } = opts;
      // [...]
    }),
  list: publicProcedure.query(() => {
    // ...
    return [];
  }),
});
```

```ts no-check title="routers/_app.ts"
import { router } from '../trpc';

import { userRouter } from './user';
import { postRouter } from './post';

const appRouter = router({
  user: userRouter,
  post: postRouter,
});

appRouter.user

appRouter.post

export type AppRouter = typeof appRouter;
```

From https://trpc.io/docs/server/merging-routers#merging-with-child-routers

## Dynamically load routers

You can use the `lazy` function to dynamically load your routers. This can be useful to reduce cold starts of your application. There's no difference in how you use the router after it's been lazy loaded vs. how you use a normal router.

```ts no-check title="routers/greeting.ts"
import { router, publicProcedure } from '../trpc';

export const greetingRouter = router({
  hello: publicProcedure.query(() => 'world'),
});
```

```ts no-check title="routers/user.ts"
import { router, publicProcedure } from '../trpc';

export const userRouter = router({
  list: publicProcedure.query(() => ['John', 'Jane', 'Jim']),
});
```

```ts no-check title="routers/_app.ts"
import { lazy } from '@trpc/server';
import { router } from '../trpc';

export const appRouter = router({
  // Option 1: Short-hand when the module has exactly 1 router exported
  greeting: lazy(() => import('./greeting.js')),
  // Option 2: if exporting more than 1 router
  user: lazy(() => import('./user.js').then((m) => m.userRouter)),
});
export type AppRouter = typeof appRouter;
```

From https://trpc.io/docs/server/merging-routers#lazy-load

## References

- [Define Routers](references/server-routers.md)
- [Define Procedures](references/server-procedures.md)
- [Context](references/server-context.md)
- [Merging Routers](references/server-merging-routers.md)
- [Metadata](references/server-metadata.md)
