0Pricing
TypeScript Academy · Lesson

Middleware and Context

Add auth and shared context to procedures.

Middleware and Context is a free TypeScript Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is Context

Every tRPC request carries a context: shared data available to all procedures, such as the database client, the request, and the current user. You build it once per request with a createContext function.

export async function createContext({ req }: { req: Request }) {
  const token = req.headers.get('authorization');
  const user = await getUserFromToken(token);
  return { user };
}

Typing the Context

tRPC infers the context type from createContext and threads it through every procedure. You initialize the instance with that context type so resolvers see ctx typed correctly.

import { initTRPC } from '@trpc/server';

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

Reading ctx in a Resolver

Inside any procedure, the context is available as ctx, fully typed. Here a procedure reads the current user from context.

const me = t.procedure.query(({ ctx }) => {
  // ctx.user is typed from createContext
  return ctx.user;
});

What Is Middleware

Middleware runs before a procedure resolver. It can check permissions, log, or modify the context. It either calls next() to continue or throws to reject the request.

const logger = t.middleware(async ({ path, next }) => {
  console.log('calling', path);
  return next();
});

An Auth Middleware

A common middleware enforces authentication. If there is no user in context, it throws an UNAUTHORIZED error; otherwise it continues to the resolver.

import { TRPCError } from '@trpc/server';

const isAuthed = t.middleware(({ ctx, next }) => {
  if (!ctx.user) {
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }
  return next();
});

Narrowing Context With next

Middleware can refine the context type by passing a new ctx to next. After the auth check, the user is guaranteed present, so we narrow user from possibly-null to non-null.

const isAuthed = t.middleware(({ ctx, next }) => {
  if (!ctx.user) throw new TRPCError({ code: 'UNAUTHORIZED' });
  return next({ ctx: { user: ctx.user } }); // user now non-null
});

Building a Protected Procedure

Attach the middleware with .use to create a reusable protected procedure. Any procedure built from it requires authentication and sees a non-null user.

export const protectedProcedure = t.procedure.use(isAuthed);

const secret = protectedProcedure.query(({ ctx }) => {
  return 'Hello ' + ctx.user.name; // user is guaranteed
});

Public vs Protected

You now have two building blocks: publicProcedure for open endpoints and protectedProcedure for authenticated ones. Choosing the right base documents and enforces access at the type and runtime level.

const ping = publicProcedure.query(() => 'pong');
const account = protectedProcedure.query(({ ctx }) => ctx.user);

Typed ctx Propagation

The narrowed context propagates: because protectedProcedure guaranteed ctx.user, every resolver built on it sees the non-null type without re-checking. The middleware encodes the invariant once.

// In any protectedProcedure resolver:
// ctx.user is User, not User | null

Composing Middleware

Middleware composes by chaining .use. You might apply logging, then auth, then a role check. Each runs in order, and each can further narrow the context for the next.

const adminProcedure = protectedProcedure.use(({ ctx, next }) => {
  if (ctx.user.role !== 'admin') {
    throw new TRPCError({ code: 'FORBIDDEN' });
  }
  return next();
});

The Full Picture

Context supplies per-request data, middleware guards and refines it, and specialized procedures bake those guarantees in. Combined with input schemas and the typed client, you get an API that is safe at runtime and at compile time, end to end.

// createContext -> ctx
// middleware -> auth + narrowing
// protectedProcedure -> guaranteed ctx.user
// typed client -> end-to-end safety

Quick Check

Test your understanding of context and middleware.

Recap

You secured a tRPC API with context and middleware.

  • createContext builds per-request shared data, typed via initTRPC.context.
  • Middleware runs before resolvers to guard and refine context.
  • Passing ctx to next narrows its type.
  • protectedProcedure bakes in auth so resolvers get a non-null user.

You have completed the end-to-end type safety course.

Frequently asked questions

Is the “Middleware and Context” lesson free?

Yes — the full text of “Middleware and Context” is free to read here on the web, and the TypeScript Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO.

What will I learn in “Middleware and Context”?

Add auth and shared context to procedures. You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start TypeScript Academy?

No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Middleware and Context” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this TypeScript Academy lesson?

Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The tRPC Architecture
  2. Defining Routers and Procedures
  3. Client-Server Type Inference
  4. Middleware and Context
← Back to TypeScript Academy