new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

tRPC · all subjects

context and middleware

48 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

createContext function called for each request

The createContext function is called for each incoming request, allowing you to add contextual information about the calling user from the request object.

Extract user from authorization header in context

In the createContext function, you can extract JWT tokens from req.headers.authorization by splitting on the space character (e.g., 'Bearer token' becomes 'token') and then decoding and verifying the token to get user information.

Export Context type from createContext

Define and export a Context type as Awaited<ReturnType<typeof createContext>> to ensure type safety across your application and enable end-to-end type inference.

Authorization in procedure resolver

Option 1: Implement authorization directly in a procedure resolver by checking opts.ctx.user and throwing a TRPCError with code 'UNAUTHORIZED' if the user is not authenticated.

Authorization using middleware with protectedProcedure

Option 2: Create a reusable protectedProcedure using t.procedure.use() with an async middleware that checks ctx.user and throws TRPCError with code 'UNAUTHORIZED' if not authenticated. The middleware can then return opts.next() with a narrowed context type where user is guaranteed to be non-null, providing better type safety.

CreateHTTPContextOptions type for standalone adapter

When using the standalone adapter, import CreateHTTPContextOptions from @trpc/server/adapters/standalone to type the createContext function parameters, which provides req and res objects.

Example: Authorization context with JWT extraction

import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone'; import { decodeAndVerifyJwtToken } from './utils'; export async function createContext({ req, res }: CreateHTTPContextOptions) { async function getUserFromHeader() { if (req.headers.authorization) { const user = await decodeAndVerifyJwtToken( req.headers.authorization.split(' ')[1], ); return user; } return null; } const user = await getUserFromHeader(); return { user, }; } export type Context = Awaited<ReturnType<typeof createContext>>;

Example: Authorization with protectedProcedure middleware

import { initTRPC, TRPCError } from '@trpc/server'; type Context = { user: { name: string } | null }; export const t = initTRPC.context<Context>().create(); export const protectedProcedure = t.procedure.use( async function isAuthed(opts) { const { ctx } = opts; if (!ctx.user) { throw new TRPCError({ code: 'UNAUTHORIZED' }); } return opts.next({ ctx: { user: ctx.user, }, }); }, ); t.router({ hello: t.procedure .input(z.string().nullish()) .query((opts) => `hello ${opts.input ?? opts.ctx.user?.name ?? 'world'}`), admin: t.router({ secret: protectedProcedure.query((opts) => { return { secret: 'sauce', }; }), }), });

Context holds data accessible to all procedures

The context holds data that all of your tRPC procedures will have access to. It is a great place to put things like authentication information.

Context setup requires two steps

Setting up the context is done in 2 steps: defining the type during initialization and then creating the runtime context for each request.

Define context type with initTRPC.context<TContext>()

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.

createContext called once per request

The `createContext()` function must be passed to the handler mounting your appRouter. The `createContext()` function is called once per request, so all procedures within a single batched request share the same context.

Context definition with CreateHTTPContextOptions

Example showing how to define context: import `CreateHTTPContextOptions` from `@trpc/server/adapters/standalone`, create an async `createContext` function that receives `opts: CreateHTTPContextOptions`, extract data from `opts.req` (the HTTP request), and export a `Context` type as `Awaited<ReturnType<typeof createContext>>`.

Pass createContext to createHTTPHandler

When using HTTP request handling with the standalone adapter, pass the `createContext` function to `createHTTPHandler` along with your router: `createHTTPHandler({ router: appRouter, createContext })`.

Use createCaller for server-side calls

For server-side calls without HTTP, create a caller factory using `t.createCallerFactory(appRouter)`, then call it with the context: `const caller = createCaller(await createContext())`.

Inner context definition and usage

Inner context is where you define context which doesn't depend on the request, such as database connections. It can be used for integration testing or server-side calls where you don't have a request object. Whatever is defined in inner context will always be available in your procedures.

Outer context definition and usage

Outer context is where you define context which depends on the request, such as a user's session. Whatever is defined in outer context is only available for procedures that are called via HTTP.

Infer Context type from inner context only

When splitting context into inner and outer, it is important to infer your `Context` type from the inner context only, as only what is defined there is really always available in your procedures.

Inner and outer context pattern example

Example showing inner/outer context pattern: define `createContextInner` with `CreateInnerContextOptions` interface containing fields like `session`, define `createContext` that calls `createContextInner` and adds request-dependent fields like `req` and `res`, export `Context` type from the return type of `createContextInner`.

Prisma client in createContextInner can increase type-checking overhead

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. An alternative is to keep context smaller and import the client directly at call sites where needed.

Create reusable procedure that checks for req and res

To avoid checking `req` or `res` for `undefined` in procedures repeatedly, you can build a small reusable procedure that validates these exist and returns a context where they are guaranteed to be truthy.

Set meta on individual procedures with .meta()

Call `.meta({ /* metadata object */ })` on a procedure to set its metadata. The metadata object must conform to the Meta type defined during initTRPC setup.

Procedure metadata property for middleware access

Procedures can have an optional `meta` property that is available in middleware function parameters. This allows you to attach metadata to procedures that middleware can access and act upon.

Create typed metadata with initTRPC

To use typed metadata, call `.meta<Meta>()` on the initTRPC instance after `.context<Context>()` and before `.create()`. Define a Meta interface with your metadata fields, then pass it as a type parameter to the meta method.

Metadata available in middleware opts parameter

Inside a middleware function, the meta property is available in the opts parameter. You can access it with `const { meta, next, ctx } = opts` to check metadata values and make decisions based on them.

Default metadata values with defaultMeta

Pass a `defaultMeta` option to the `.create()` method to set default values for metadata on all procedures. Use `initTRPC.context<Context>().meta<Meta>().create({ defaultMeta: { /* defaults */ } })`.

Metadata shallow merging when chaining procedures

When chaining `.meta()` calls on procedures, the metadata is shallow merged. If you call `.meta()` on a base procedure and then again on a derived procedure, the second call's metadata will be merged with the first, with duplicate keys taking the value from the most recent call.

Per-route authentication with metadata example

Create an authed procedure that uses middleware to check `meta?.authRequired`. If authRequired is true and the user is not authenticated, throw a TRPCError with code 'UNAUTHORIZED'. Different routes can set authRequired to true or false on their own .meta() calls.

Authorization middleware example with adminProcedure

Example: Create an adminProcedure by calling publicProcedure.use() with a middleware that checks if ctx.user?.isAdmin is true. If not, throw new TRPCError({ code: 'UNAUTHORIZED' }). If authorized, call opts.next() with ctx: { user: ctx.user }.

concat() creates reusable middlewares and plugins independent of Context type

tRPC has an API called .concat() which allows you to independently define a partial procedure that can be used with any tRPC instance that matches the context and metadata of the plugin. This helps create plugins and libraries with tRPC. When creating a plugin, use initTRPC with a minimal context definition. In the consuming app, call publicProcedure.concat(plugin.pluginProc) to compose the middleware.

Add middleware to procedure with t.procedure.use()

You can add middleware(s) to a procedure with the t.procedure.use() method. The middleware(s) will wrap the invocation of the procedure and must call opts.next() and return its result.

Context Extension example narrowing nullable user to non-null

Example: Create a protectedProcedure using publicProcedure.use() where the base context has user?: { id: string }. The middleware checks if ctx.user is falsy and throws TRPCError({ code: 'UNAUTHORIZED' }) if so. When calling opts.next({ ctx: { user: ctx.user } }), TypeScript narrows the type so downstream procedures access a non-null user.

Context Extension enables type-safe context modifications

Context Extension enables middlewares to dynamically add and override keys on a base procedure's context in a typesafe manner. Middlewares can call opts.next({ ctx: { ...newContextProperties } }) to extend the context, and TypeScript will narrow the context type for all chained consumers including other middlewares and procedures.

Logging middleware example with timing

Example: Create a loggedProcedure using publicProcedure.use() with a middleware that captures Date.now() before calling opts.next(), then logs the duration, path (opts.path), type (opts.type), and whether the result was ok (result.ok) after execution.

concat() plugin example with type-safe context merging

Example: A plugin calls initTRPC.context<{}>().create() to define plugin root t-object. The plugin exports pluginProc: t.procedure.use() which adds ctx.fromPlugin: 'hello from myPlugin'. The consuming app initializes the plugin with const plugin = createMyPlugin() and creates a base procedure using publicProcedure.concat(plugin.pluginProc). The procedure's context now includes fromPlugin from the plugin.

unstable_pipe() extends middlewares in a type-safe manner

tRPC has a feature called .pipe() which allows you to extend middlewares in a typesafe manner. A middleware can call middleware.unstable_pipe() with another middleware function to chain them. The piped middleware receives the context from the previous middleware and can add to it by calling opts.next({ ctx: { ...newProperties } }).

unstable_pipe() middleware extension example

Example: Create fooMiddleware using t.middleware() that adds ctx.foo: 'foo'. Then create barMiddleware using fooMiddleware.unstable_pipe() which accesses ctx.foo and adds ctx.bar: 'bar'. Finally create barProcedure using publicProcedure.use(barMiddleware). The query handler can access both ctx.foo and ctx.bar.

unstable_pipe() order and context overlap requirements

The order in which you pipe middlewares matters and the context must overlap. If fooMiddleware modifies ctx.a from an object to a string, and barMiddleware expects ctx.a to be an object, piping fooMiddleware.unstable_pipe(barMiddleware) will fail type checking. The middleware being piped must not override context properties that the next middleware expects to receive unchanged.

experimental_standaloneMiddleware() deprecated in favor of concat()

experimental_standaloneMiddleware() has been deprecated in favor of .concat(). For new code, use .concat() instead.

experimental_standaloneMiddleware() creates context-independent middleware

experimental_standaloneMiddleware() allows you to independently define a middleware that can be used with any tRPC instance. When creating the middleware, explicitly define Context, Input, and Meta types using generics: experimental_standaloneMiddleware<{ ctx: { ... }; input: { ... } }>().create(). The middleware can then be used with different tRPC instances that satisfy those type requirements.

experimental_standaloneMiddleware() type validation example

Example: Create projectAccessMiddleware using experimental_standaloneMiddleware<{ ctx: { allowedProjects: string[] }; input: { projectId: string } }>().create(). The middleware checks if opts.ctx.allowedProjects.includes(opts.input.projectId). When used with t1.procedure.input(z.object({ projectId: z.string() })).use(projectAccessMiddleware), it type-checks successfully. If the input defines projectId as z.number() or the context has allowedProjects: number[], type checking fails.

experimental_standaloneMiddleware() multiple middlewares example

Example: Create valueAUppercaserMiddleware and valueBUppercaserMiddleware using experimental_standaloneMiddleware with different input requirements. Chain them on a single procedure using .input(combinedInputThatSatisfiesBothMiddlewares).use(valueAUppercaserMiddleware).use(valueBUppercaserMiddleware). The combined input schema must satisfy both middleware input requirements. The query handler receives both ctx.valueAUppercase and ctx.valueBUppercase.

Using .use() middleware to build reusable procedures

Base procedures are created by calling .use() with a middleware function that receives opts containing ctx and input, and returns opts.next() with modified context. For example, authedProcedure uses .use() to assert ctx.user is not null, and throws TRPCError with code 'UNAUTHORIZED' if it is.

Context type is defined when initializing tRPC

The context type is specified when initializing tRPC using initTRPC.context<ContextType>().create(), defining what properties are available in opts.ctx throughout procedures and middleware.

createCaller with middleware example

Example showing middleware execution with createCaller: ```ts type Context = { user?: { id: string } }; const t = initTRPC.context<Context>().create(); const protectedProcedure = t.procedure.use((opts) => { const { ctx } = opts; if (!ctx.user) { throw new TRPCError({ code: 'UNAUTHORIZED', message: 'You are not authorized' }); } return opts.next({ ctx: { user: ctx.user } }); }); const router = t.router({ secret: protectedProcedure.query((opts) => opts.ctx.user), }); // This fails - no user in context const caller = router.createCaller({}); // This works - user is present const authorizedCaller = router.createCaller({ user: { id: 'KATT' } }); const result = await authorizedCaller.secret(); ```

Middleware execution with createCaller

Middlewares are executed before any procedure is called when using createCaller. If a procedure is protected by middleware that requires specific context properties (like a user object), you must pass the appropriate context to createCaller or the middleware will reject the call.

tRPC middleware and guards

Add middleware to procedures using .use(), implement auth guards, logging, and base procedures. Refer to middlewares skill for full patterns.

tRPC authentication patterns

Implement auth middleware, client headers for authentication, and subscription authentication. Refer to auth skill for full patterns.

Give your agent this brain