Middlewares
You are able to add middleware(s) to a procedure with the t.procedure.use()
method. The middleware(s) will wrap the invocation of the procedure and must pass through its return value.
Authorization
In the example below, any call to a adminProcedure
will ensure that the user is an "admin" before executing.
ts
import {TRPCError ,initTRPC } from '@trpc/server';interfaceContext {user ?: {id : string;isAdmin : boolean;// [..]};}constt =initTRPC .context <Context >().create ();export constpublicProcedure =t .procedure ;export constrouter =t .router ;export constadminProcedure =publicProcedure .use (async (opts ) => {const {ctx } =opts ;if (!ctx .user ?.isAdmin ) {throw newTRPCError ({code : 'UNAUTHORIZED' });}returnopts .next ({ctx : {user :ctx .user ,},});});
ts
import {TRPCError ,initTRPC } from '@trpc/server';interfaceContext {user ?: {id : string;isAdmin : boolean;// [..]};}constt =initTRPC .context <Context >().create ();export constpublicProcedure =t .procedure ;export constrouter =t .router ;export constadminProcedure =publicProcedure .use (async (opts ) => {const {ctx } =opts ;if (!ctx .user ?.isAdmin ) {throw newTRPCError ({code : 'UNAUTHORIZED' });}returnopts .next ({ctx : {user :ctx .user ,},});});
ts
import {adminProcedure ,publicProcedure ,router } from './trpc';constadminRouter =router ({secretPlace :adminProcedure .query (() => 'a key'),});export constappRouter =router ({foo :publicProcedure .query (() => 'bar'),admin :adminRouter ,});
ts
import {adminProcedure ,publicProcedure ,router } from './trpc';constadminRouter =router ({secretPlace :adminProcedure .query (() => 'a key'),});export constappRouter =router ({foo :publicProcedure .query (() => 'bar'),admin :adminRouter ,});
See Error Handling to learn more about the TRPCError
thrown in the above example.
Logging
In the example below timings for queries are logged automatically.
ts
export constloggedProcedure =publicProcedure .use (async (opts ) => {conststart =Date .now ();constresult = awaitopts .next ();constdurationMs =Date .now () -start ;constmeta = {path :opts .path ,type :opts .type ,durationMs };result .ok ?console .log ('OK request timing:',meta ):console .error ('Non-OK request timing',meta );returnresult ;});
ts
export constloggedProcedure =publicProcedure .use (async (opts ) => {conststart =Date .now ();constresult = awaitopts .next ();constdurationMs =Date .now () -start ;constmeta = {path :opts .path ,type :opts .type ,durationMs };result .ok ?console .log ('OK request timing:',meta ):console .error ('Non-OK request timing',meta );returnresult ;});
ts
import { loggedProcedure, router } from './trpc';export const appRouter= router({ foo: loggedProcedure.query(() => 'bar'),abc:loggedProcedure .query (() => 'def'), });
ts
import { loggedProcedure, router } from './trpc';export const appRouter= router({ foo: loggedProcedure.query(() => 'bar'),abc: loggedProcedure. query (() => 'def'), });
Context Extension
"Context Extension" enables middlewares to dynamically add and override keys on a base procedure's context in a typesafe manner.
Below we have an example of a middleware that changes properties of a context, the changes are then available to all chained consumers, such as other middlewares and procedures:
ts
typeContext = {// user is nullableuser ?: {id : string;};};constprotectedProcedure =publicProcedure .use (async functionisAuthed (opts ) {const {ctx } =opts ;// `ctx.user` is nullableif (!ctx .user ) {throw newTRPCError ({code : 'UNAUTHORIZED' });}returnopts .next ({ctx : {// ✅ user value is known to be non-null nowuser :ctx .user ,},});});protectedProcedure .query (({ctx }) =>ctx .user );
ts
typeContext = {// user is nullableuser ?: {id : string;};};constprotectedProcedure =publicProcedure .use (async functionisAuthed (opts ) {const {ctx } =opts ;// `ctx.user` is nullableif (!ctx .user ) {throw newTRPCError ({code : 'UNAUTHORIZED' });}returnopts .next ({ctx : {// ✅ user value is known to be non-null nowuser :ctx .user ,},});});protectedProcedure .query (({ctx }) =>ctx .user );
Extending middlewares
We have prefixed this as unstable_
as it's a new API, but you're safe to use it! Read more.
We have a powerful feature called .pipe()
which allows you to extend middlewares in a typesafe manner.
Below we have an example of a middleware that extends a base middleware(foo). Like the context extension example above, piping middlewares will change properties of the context, and procedures will receive the new context value.
ts
constfooMiddleware =t .middleware ((opts ) => {returnopts .next ({ctx : {foo : 'foo' asconst ,},});});constbarMiddleware =fooMiddleware .unstable_pipe ((opts ) => {const {ctx } =opts ;ctx .foo ;returnopts .next ({ctx : {bar : 'bar' asconst ,},});});constbarProcedure =publicProcedure .use (barMiddleware );barProcedure .query (({ctx }) =>ctx .bar );
ts
constfooMiddleware =t .middleware ((opts ) => {returnopts .next ({ctx : {foo : 'foo' asconst ,},});});constbarMiddleware =fooMiddleware .unstable_pipe ((opts ) => {const {ctx } =opts ;ctx .foo ;returnopts .next ({ctx : {bar : 'bar' asconst ,},});});constbarProcedure =publicProcedure .use (barMiddleware );barProcedure .query (({ctx }) =>ctx .bar );
Beware that the order in which you pipe your middlewares matter and that the context must overlap. An example of a forbidden pipe is shown below. Here, the fooMiddleware
overrides the ctx.a
while barMiddleware
still expects the root context from the initialization in initTRPC
- so piping fooMiddleware
with barMiddleware
would not work, while piping barMiddleware
with fooMiddleware
does work.
ts
import {initTRPC } from '@trpc/server';constt =initTRPC .context <{a : {b : 'a';};}>().create ();constfooMiddleware =t .middleware ((opts ) => {const {ctx } =opts ;ctx .a ; // 👈 fooMiddleware expects `ctx.a` to be an objectreturnopts .next ({ctx : {a : 'a' asconst , // 👈 `ctx.a` is no longer an object},});});constbarMiddleware =t .middleware ((opts ) => {const {ctx } =opts ;ctx .a ; // 👈 barMiddleware expects `ctx.a` to be an objectreturnopts .next ({ctx : {foo : 'foo' asconst ,},});});// ❌ `ctx.a` does not overlap from `fooMiddleware` to `barMiddleware`Argument of type 'MiddlewareBuilder<{ a: { b: "a"; }; }, object, { foo: "foo"; }, unknown>' is not assignable to parameter of type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { a: "a"; }, { foo: "foo"; }, unknown> | MiddlewareBuilder<{ a: "a"; }, object, { foo: "foo"; }, unknown>'. Type 'MiddlewareBuilder<{ a: { b: "a"; }; }, object, { foo: "foo"; }, unknown>' is not assignable to type 'MiddlewareBuilder<{ a: "a"; }, object, { foo: "foo"; }, unknown>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, $ContextOverridesOut, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, $ContextOverridesOut, unknown>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, $ContextOverridesOut, unknown> | MiddlewareBuilder<{ a: "a"; foo: "foo"; }, object, $ContextOverridesOut, unknown>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: "a"; foo: "foo"; }, object, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, any, unknown>'. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, any, unknown>'. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { a: { b: "a"; }; foo: "foo"; }; type: "query" | "mutation" | "subscription"; path: string; input: unknown; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }' is not assignable to type '{ ctx: { a: "a"; foo: "foo"; }; type: "query" | "mutation" | "subscription"; path: string; input: unknown; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }'. The types of 'ctx.a' are incompatible between these types. Type '{ b: "a"; }' is not assignable to type '"a"'.2345Argument of type 'MiddlewareBuilder<{ a: { b: "a"; }; }, object, { foo: "foo"; }, unknown>' is not assignable to parameter of type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { a: "a"; }, { foo: "foo"; }, unknown> | MiddlewareBuilder<{ a: "a"; }, object, { foo: "foo"; }, unknown>'. Type 'MiddlewareBuilder<{ a: { b: "a"; }; }, object, { foo: "foo"; }, unknown>' is not assignable to type 'MiddlewareBuilder<{ a: "a"; }, object, { foo: "foo"; }, unknown>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, $ContextOverridesOut, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, $ContextOverridesOut, unknown>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, $ContextOverridesOut, unknown> | MiddlewareBuilder<{ a: "a"; foo: "foo"; }, object, $ContextOverridesOut, unknown>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: "a"; foo: "foo"; }, object, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, any, unknown>'. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, any, unknown>'. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { a: { b: "a"; }; foo: "foo"; }; type: "query" | "mutation" | "subscription"; path: string; input: unknown; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }' is not assignable to type '{ ctx: { a: "a"; foo: "foo"; }; type: "query" | "mutation" | "subscription"; path: string; input: unknown; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }'. The types of 'ctx.a' are incompatible between these types. Type '{ b: "a"; }' is not assignable to type '"a"'.fooMiddleware .unstable_pipe (); barMiddleware // ✅ `ctx.a` overlaps from `barMiddleware` and `fooMiddleware`barMiddleware .unstable_pipe (fooMiddleware );
ts
import {initTRPC } from '@trpc/server';constt =initTRPC .context <{a : {b : 'a';};}>().create ();constfooMiddleware =t .middleware ((opts ) => {const {ctx } =opts ;ctx .a ; // 👈 fooMiddleware expects `ctx.a` to be an objectreturnopts .next ({ctx : {a : 'a' asconst , // 👈 `ctx.a` is no longer an object},});});constbarMiddleware =t .middleware ((opts ) => {const {ctx } =opts ;ctx .a ; // 👈 barMiddleware expects `ctx.a` to be an objectreturnopts .next ({ctx : {foo : 'foo' asconst ,},});});// ❌ `ctx.a` does not overlap from `fooMiddleware` to `barMiddleware`Argument of type 'MiddlewareBuilder<{ a: { b: "a"; }; }, object, { foo: "foo"; }, unknown>' is not assignable to parameter of type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { a: "a"; }, { foo: "foo"; }, unknown> | MiddlewareBuilder<{ a: "a"; }, object, { foo: "foo"; }, unknown>'. Type 'MiddlewareBuilder<{ a: { b: "a"; }; }, object, { foo: "foo"; }, unknown>' is not assignable to type 'MiddlewareBuilder<{ a: "a"; }, object, { foo: "foo"; }, unknown>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, $ContextOverridesOut, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, $ContextOverridesOut, unknown>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, $ContextOverridesOut, unknown> | MiddlewareBuilder<{ a: "a"; foo: "foo"; }, object, $ContextOverridesOut, unknown>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: "a"; foo: "foo"; }, object, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, any, unknown>'. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, any, unknown>'. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { a: { b: "a"; }; foo: "foo"; }; type: "query" | "mutation" | "subscription"; path: string; input: unknown; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }' is not assignable to type '{ ctx: { a: "a"; foo: "foo"; }; type: "query" | "mutation" | "subscription"; path: string; input: unknown; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }'. The types of 'ctx.a' are incompatible between these types. Type '{ b: "a"; }' is not assignable to type '"a"'.2345Argument of type 'MiddlewareBuilder<{ a: { b: "a"; }; }, object, { foo: "foo"; }, unknown>' is not assignable to parameter of type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { a: "a"; }, { foo: "foo"; }, unknown> | MiddlewareBuilder<{ a: "a"; }, object, { foo: "foo"; }, unknown>'. Type 'MiddlewareBuilder<{ a: { b: "a"; }; }, object, { foo: "foo"; }, unknown>' is not assignable to type 'MiddlewareBuilder<{ a: "a"; }, object, { foo: "foo"; }, unknown>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, $ContextOverridesOut, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, $ContextOverridesOut, unknown>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, $ContextOverridesOut, unknown> | MiddlewareBuilder<{ a: "a"; foo: "foo"; }, object, $ContextOverridesOut, unknown>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: "a"; foo: "foo"; }, object, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, any, unknown>'. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, any, unknown>'. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { a: { b: "a"; }; foo: "foo"; }; type: "query" | "mutation" | "subscription"; path: string; input: unknown; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }' is not assignable to type '{ ctx: { a: "a"; foo: "foo"; }; type: "query" | "mutation" | "subscription"; path: string; input: unknown; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }'. The types of 'ctx.a' are incompatible between these types. Type '{ b: "a"; }' is not assignable to type '"a"'.fooMiddleware .unstable_pipe (); barMiddleware // ✅ `ctx.a` overlaps from `barMiddleware` and `fooMiddleware`barMiddleware .unstable_pipe (fooMiddleware );
Experimental: standalone middlewares
Caution: we have prefixed this as experimental_
and it may change with any tRPC release. Read more.
tRPC has a new experimental API called experimental_standaloneMiddleware
which allows you to independently define a middleware that can be used with any tRPC instance. Creating middlewares using t.middleware
has the limitation that
the Context
type is tied to the Context
type of the tRPC instance. This means that you cannot use the same middleware with multiple tRPC instances that have different Context
types.
Using experimental_standaloneMiddleware
you can create a middleware that explicitly defines its requirements, i.e. the Context, Input and Meta types:
ts
import {experimental_standaloneMiddleware ,initTRPC ,TRPCError ,} from '@trpc/server';import * asz from 'zod';constprojectAccessMiddleware =experimental_standaloneMiddleware <{ctx : {allowedProjects : string[] }; // defaults to 'object' if not definedinput : {projectId : string }; // defaults to 'unknown' if not defined// 'meta', not defined here, defaults to 'object | undefined'}>().create ((opts ) => {if (!opts .ctx .allowedProjects .includes (opts .input .projectId )) {throw newTRPCError ({code : 'FORBIDDEN',message : 'Not allowed',});}returnopts .next ();});constt1 =initTRPC .context <{allowedProjects : string[];}>().create ();// ✅ `ctx.allowedProjects` satisfies "string[]" and `input.projectId` satisfies "string"constaccessControlledProcedure =t1 .procedure .input (z .object ({projectId :z .string () })).use (projectAccessMiddleware );// ❌ `ctx.allowedProjects` satisfies "string[]" but `input.projectId` does not satisfy "string"constaccessControlledProcedure2 =t1 .procedure .input (z .object ({projectId :z .number () })).Argument of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to parameter of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: number; }> | MiddlewareFunction<{ allowedProjects: string[]; }, object, object, object, { projectId: number; }>'. Type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: number; }>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: number; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }' is not assignable to type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: number; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }'. The types of 'input.projectId' are incompatible between these types. Type 'string' is not assignable to type 'number'.2345Argument of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to parameter of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: number; }> | MiddlewareFunction<{ allowedProjects: string[]; }, object, object, object, { projectId: number; }>'. Type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: number; }>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: number; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }' is not assignable to type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: number; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }'. The types of 'input.projectId' are incompatible between these types. Type 'string' is not assignable to type 'number'.use (); projectAccessMiddleware // ❌ `ctx.allowedProjects` does not satisfy "string[]" even though `input.projectId` satisfies "string"constt2 =initTRPC .context <{allowedProjects : number[];}>().create ();constaccessControlledProcedure3 =t2 .procedure .input (z .object ({projectId :z .string () })).Argument of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to parameter of type 'MiddlewareBuilder<{ allowedProjects: number[]; }, object, object, { projectId: string; }> | MiddlewareFunction<{ allowedProjects: number[]; }, object, object, object, { projectId: string; }>'. Type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to type 'MiddlewareBuilder<{ allowedProjects: number[]; }, object, object, { projectId: string; }>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: number[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: number[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: number[]; }, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }' is not assignable to type '{ ctx: { allowedProjects: number[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }'. The types of 'ctx.allowedProjects' are incompatible between these types. Type 'string[]' is not assignable to type 'number[]'. Type 'string' is not assignable to type 'number'.2345Argument of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to parameter of type 'MiddlewareBuilder<{ allowedProjects: number[]; }, object, object, { projectId: string; }> | MiddlewareFunction<{ allowedProjects: number[]; }, object, object, object, { projectId: string; }>'. Type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to type 'MiddlewareBuilder<{ allowedProjects: number[]; }, object, object, { projectId: string; }>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: number[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: number[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: number[]; }, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }' is not assignable to type '{ ctx: { allowedProjects: number[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }'. The types of 'ctx.allowedProjects' are incompatible between these types. Type 'string[]' is not assignable to type 'number[]'. Type 'string' is not assignable to type 'number'.use (); projectAccessMiddleware
ts
import {experimental_standaloneMiddleware ,initTRPC ,TRPCError ,} from '@trpc/server';import * asz from 'zod';constprojectAccessMiddleware =experimental_standaloneMiddleware <{ctx : {allowedProjects : string[] }; // defaults to 'object' if not definedinput : {projectId : string }; // defaults to 'unknown' if not defined// 'meta', not defined here, defaults to 'object | undefined'}>().create ((opts ) => {if (!opts .ctx .allowedProjects .includes (opts .input .projectId )) {throw newTRPCError ({code : 'FORBIDDEN',message : 'Not allowed',});}returnopts .next ();});constt1 =initTRPC .context <{allowedProjects : string[];}>().create ();// ✅ `ctx.allowedProjects` satisfies "string[]" and `input.projectId` satisfies "string"constaccessControlledProcedure =t1 .procedure .input (z .object ({projectId :z .string () })).use (projectAccessMiddleware );// ❌ `ctx.allowedProjects` satisfies "string[]" but `input.projectId` does not satisfy "string"constaccessControlledProcedure2 =t1 .procedure .input (z .object ({projectId :z .number () })).Argument of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to parameter of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: number; }> | MiddlewareFunction<{ allowedProjects: string[]; }, object, object, object, { projectId: number; }>'. Type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: number; }>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: number; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }' is not assignable to type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: number; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }'. The types of 'input.projectId' are incompatible between these types. Type 'string' is not assignable to type 'number'.2345Argument of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to parameter of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: number; }> | MiddlewareFunction<{ allowedProjects: string[]; }, object, object, object, { projectId: number; }>'. Type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: number; }>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: number; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }' is not assignable to type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: number; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }'. The types of 'input.projectId' are incompatible between these types. Type 'string' is not assignable to type 'number'.use (); projectAccessMiddleware // ❌ `ctx.allowedProjects` does not satisfy "string[]" even though `input.projectId` satisfies "string"constt2 =initTRPC .context <{allowedProjects : number[];}>().create ();constaccessControlledProcedure3 =t2 .procedure .input (z .object ({projectId :z .string () })).Argument of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to parameter of type 'MiddlewareBuilder<{ allowedProjects: number[]; }, object, object, { projectId: string; }> | MiddlewareFunction<{ allowedProjects: number[]; }, object, object, object, { projectId: string; }>'. Type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to type 'MiddlewareBuilder<{ allowedProjects: number[]; }, object, object, { projectId: string; }>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: number[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: number[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: number[]; }, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }' is not assignable to type '{ ctx: { allowedProjects: number[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }'. The types of 'ctx.allowedProjects' are incompatible between these types. Type 'string[]' is not assignable to type 'number[]'. Type 'string' is not assignable to type 'number'.2345Argument of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to parameter of type 'MiddlewareBuilder<{ allowedProjects: number[]; }, object, object, { projectId: string; }> | MiddlewareFunction<{ allowedProjects: number[]; }, object, object, object, { projectId: string; }>'. Type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to type 'MiddlewareBuilder<{ allowedProjects: number[]; }, object, object, { projectId: string; }>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: number[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: number[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: number[]; }, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }' is not assignable to type '{ ctx: { allowedProjects: number[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; next: { ...; }; }'. The types of 'ctx.allowedProjects' are incompatible between these types. Type 'string[]' is not assignable to type 'number[]'. Type 'string' is not assignable to type 'number'.use (); projectAccessMiddleware
Here is an example with multiple standalone middlewares:
ts
import {experimental_standaloneMiddleware ,initTRPC } from '@trpc/server';import * asz from 'zod';constt =initTRPC .create ();constschemaA =z .object ({valueA :z .string () });constschemaB =z .object ({valueB :z .string () });constvalueAUppercaserMiddleware =experimental_standaloneMiddleware <{input :z .infer <typeofschemaA >;}>().create ((opts ) => {returnopts .next ({ctx : {valueAUppercase :opts .input .valueA .toUpperCase () },});});constvalueBUppercaserMiddleware =experimental_standaloneMiddleware <{input :z .infer <typeofschemaB >;}>().create ((opts ) => {returnopts .next ({ctx : {valueBUppercase :opts .input .valueB .toUpperCase () },});});constcombinedInputThatSatisfiesBothMiddlewares =z .object ({valueA :z .string (),valueB :z .string (),extraProp :z .string (),});t .procedure .input (combinedInputThatSatisfiesBothMiddlewares ).use (valueAUppercaserMiddleware ).use (valueBUppercaserMiddleware ).query (({input : {valueA ,valueB ,extraProp },ctx : {valueAUppercase ,valueBUppercase },}) =>`valueA: ${valueA }, valueB: ${valueB }, extraProp: ${extraProp }, valueAUppercase: ${valueAUppercase }, valueBUppercase: ${valueBUppercase }`,);
ts
import {experimental_standaloneMiddleware ,initTRPC } from '@trpc/server';import * asz from 'zod';constt =initTRPC .create ();constschemaA =z .object ({valueA :z .string () });constschemaB =z .object ({valueB :z .string () });constvalueAUppercaserMiddleware =experimental_standaloneMiddleware <{input :z .infer <typeofschemaA >;}>().create ((opts ) => {returnopts .next ({ctx : {valueAUppercase :opts .input .valueA .toUpperCase () },});});constvalueBUppercaserMiddleware =experimental_standaloneMiddleware <{input :z .infer <typeofschemaB >;}>().create ((opts ) => {returnopts .next ({ctx : {valueBUppercase :opts .input .valueB .toUpperCase () },});});constcombinedInputThatSatisfiesBothMiddlewares =z .object ({valueA :z .string (),valueB :z .string (),extraProp :z .string (),});t .procedure .input (combinedInputThatSatisfiesBothMiddlewares ).use (valueAUppercaserMiddleware ).use (valueBUppercaserMiddleware ).query (({input : {valueA ,valueB ,extraProp },ctx : {valueAUppercase ,valueBUppercase },}) =>`valueA: ${valueA }, valueB: ${valueB }, extraProp: ${extraProp }, valueAUppercase: ${valueAUppercase }, valueBUppercase: ${valueBUppercase }`,);