Middlewares
On this page
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.
Authorization
In the example below, any call to a adminProcedure will ensure that the user is an "admin" before executing.
import { class TRPCErrorTRPCError, const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';
interface Context {
Context.user?: {
id: string;
isAdmin: boolean;
} | undefined
user?: {
id: stringid: string;
isAdmin: booleanisAdmin: boolean;
// [..]
};
}
const const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.context<Context>(): TRPCBuilder<Context, object>context<Context>().TRPCBuilder<Context, object>.create<RuntimeConfigOptions<Context, object>>(opts?: RuntimeConfigOptions<Context, object> | undefined): TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
export const const publicProcedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure = const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure;
export const const router: RouterBuilder<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
router = const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
router;
export const const adminProcedure: ProcedureBuilder<Context, object, {
user: {
id: string;
isAdmin: boolean;
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
adminProcedure = const publicProcedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure.ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
user: {
id: string;
isAdmin: boolean;
};
}>(fn: MiddlewareBuilder<{
user: {
id: string;
isAdmin: boolean;
} | undefined;
}, object, {
user: {
id: string;
isAdmin: boolean;
};
}, UnsetMarker> | MiddlewareFunction<Context, object, object, {
user: {
id: string;
isAdmin: boolean;
};
}, UnsetMarker>): ProcedureBuilder<Context, object, {
user: {
id: string;
isAdmin: boolean;
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(async (opts: {
ctx: {
user: {
id: string;
isAdmin: boolean;
} | undefined;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) => {
const { const ctx: {
user: {
id: string;
isAdmin: boolean;
} | undefined;
}
ctx } = opts: {
ctx: {
user: {
id: string;
isAdmin: boolean;
} | undefined;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts;
if (!const ctx: {
user: {
id: string;
isAdmin: boolean;
} | undefined;
}
ctx.user: {
id: string;
isAdmin: boolean;
} | undefined
user?.isAdmin: boolean | undefinedisAdmin) {
throw new new TRPCError(opts: {
message?: string;
code: TRPC_ERROR_CODE_KEY;
cause?: unknown;
}): TRPCError
TRPCError({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'UNAUTHORIZED' });
}
return opts: {
ctx: {
user: {
id: string;
isAdmin: boolean;
} | undefined;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
user: {
id: string;
isAdmin: boolean;
};
}>(opts: {
ctx?: {
user: {
id: string;
isAdmin: boolean;
};
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
user: {
id: string;
isAdmin: boolean;
};
}>> (+2 overloads)
next({
ctx?: {
user: {
id: string;
isAdmin: boolean;
};
} | undefined
ctx: {
user: {
id: string;
isAdmin: boolean;
}
user: const ctx: {
user: {
id: string;
isAdmin: boolean;
} | undefined;
}
ctx.user: {
id: string;
isAdmin: boolean;
}
user,
},
});
});import { const adminProcedure: ProcedureBuilder<Context, object, {
user: {
id: string;
isAdmin: boolean;
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
adminProcedure, const publicProcedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure, const router: RouterBuilder<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
router } from './trpc';
const const adminRouter: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
secretPlace: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
adminRouter = router<{
secretPlace: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>(_: {
secretPlace: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}): BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
secretPlace: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
router({
secretPlace: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
secretPlace: const adminProcedure: ProcedureBuilder<Context, object, {
user: {
id: string;
isAdmin: boolean;
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
adminProcedure.ProcedureBuilder<Context, object, { user: { id: string; isAdmin: boolean; }; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<Context, object, {
user: {
id: string;
isAdmin: boolean;
};
}, UnsetMarker, UnsetMarker, string>): QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
query(() => 'a key'),
});
export const const appRouter: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
foo: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
admin: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
secretPlace: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>>
appRouter = router<{
foo: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
admin: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
secretPlace: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>(_: {
foo: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
admin: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
secretPlace: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}): BuiltRouter<...>
router({
foo: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
foo: const publicProcedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure.ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<Context, object, object, UnsetMarker, UnsetMarker, string>): QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
query(() => 'bar'),
admin: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
secretPlace: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
admin: const adminRouter: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
secretPlace: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
adminRouter,
});Logging
In the example below timings for queries are logged automatically.
export const const loggedProcedure: ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>loggedProcedure = const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<object>(fn: MiddlewareBuilder<{}, object, object, UnsetMarker> | MiddlewareFunction<object, object, object, object, UnsetMarker>): ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>use(async (opts: {
ctx: {};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) => {
const const start: numberstart = var Date: DateConstructorDate.DateConstructor.now(): numbernow();
const const result: MiddlewareResult<object>result = await opts: {
ctx: {};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: () => Promise<MiddlewareResult<object>> (+2 overloads)next();
const const durationMs: numberdurationMs = var Date: DateConstructorDate.DateConstructor.now(): numbernow() - const start: numberstart;
const const meta: {
path: string;
type: "query" | "mutation" | "subscription";
durationMs: number;
}
meta = { path: stringpath: opts: {
ctx: {};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.path: stringpath, type: "query" | "mutation" | "subscription"type: opts: {
ctx: {};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.type: "query" | "mutation" | "subscription"type, durationMs: numberdurationMs };
const result: MiddlewareResult<object>result.ok: booleanok
? var console: Consoleconsole.Console.log(...data: any[]): voidlog('OK request timing:', const meta: {
path: string;
type: "query" | "mutation" | "subscription";
durationMs: number;
}
meta)
: var console: Consoleconsole.Console.error(...data: any[]): voiderror('Non-OK request timing', const meta: {
path: string;
type: "query" | "mutation" | "subscription";
durationMs: number;
}
meta);
return const result: MiddlewareResult<object>result;
});import { const loggedProcedure: ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>loggedProcedure, const router: RouterBuilder<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
router } from './trpc';
export const const appRouter: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
foo: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
abc: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
appRouter = router<{
foo: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
abc: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>(_: {
foo: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
abc: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}): BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<...>>
router({
foo: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
foo: const loggedProcedure: ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>loggedProcedure.ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<object, object, {}, UnsetMarker, UnsetMarker, string>): QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
query(() => 'bar'),
abc: QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
abc: const loggedProcedure: ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>loggedProcedure.ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<object, object, {}, UnsetMarker, UnsetMarker, string>): QueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
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:
type type Context = {
user?: {
id: string;
};
}
Context = {
// user is nullable
user?: {
id: string;
} | undefined
user?: {
id: stringid: string;
};
};
const const protectedProcedure: ProcedureBuilder<Context, object, {
user: {
id: string;
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
protectedProcedure = const publicProcedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure.ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
user: {
id: string;
};
}>(fn: MiddlewareBuilder<{
user: {
id: string;
} | undefined;
}, object, {
user: {
id: string;
};
}, UnsetMarker> | MiddlewareFunction<Context, object, object, {
user: {
id: string;
};
}, UnsetMarker>): ProcedureBuilder<Context, object, {
user: {
id: string;
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(async function function (local function) isAuthed(opts: {
ctx: {
user: {
id: string;
} | undefined;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}): Promise<...>
isAuthed(opts: {
ctx: {
user: {
id: string;
} | undefined;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) {
const { const ctx: {
user: {
id: string;
} | undefined;
}
ctx } = opts: {
ctx: {
user: {
id: string;
} | undefined;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts;
// `ctx.user` is nullable
if (!const ctx: {
user: {
id: string;
} | undefined;
}
ctx.user) { throw new new TRPCError(opts: {
message?: string;
code: TRPC_ERROR_CODE_KEY;
cause?: unknown;
}): TRPCError
TRPCError({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'UNAUTHORIZED' });
}
return opts: {
ctx: {
user: {
id: string;
} | undefined;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
user: {
id: string;
};
}>(opts: {
ctx?: {
user: {
id: string;
};
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
user: {
id: string;
};
}>> (+2 overloads)
next({
ctx?: {
user: {
id: string;
};
} | undefined
ctx: {
// ✅ user value is known to be non-null now
user: const ctx: {
user: {
id: string;
} | undefined;
}
ctx.user: {
id: string;
}
user, },
});
});
const protectedProcedure: ProcedureBuilder<Context, object, {
user: {
id: string;
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
protectedProcedure.ProcedureBuilder<Context, object, { user: { id: string; }; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
id: string;
}>(resolver: ProcedureResolver<Context, object, {
user: {
id: string;
};
}, UnsetMarker, UnsetMarker, {
id: string;
}>): QueryProcedure<{
input: void;
output: {
id: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
query((opts: ProcedureResolverOptions<Context, object, {
user: {
id: string;
};
}, UnsetMarker>
opts) => {
const { const ctx: {
user: {
id: string;
};
}
ctx } = opts: ProcedureResolverOptions<Context, object, {
user: {
id: string;
};
}, UnsetMarker>
opts;
return ctx.user: {
id: string;
}
user;});Using .concat() to create reusable middlewares and plugins
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 helper primarily targets creating plugins and libraries with tRPC.
// ------------------------------------------------
// 🧩🧩🧩 a library creating a reusable plugin 🧩🧩🧩
// @filename: src/myPlugin.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC, class TRPCErrorTRPCError } from '@trpc/server';
export function function createMyPlugin(): {
pluginProc: ProcedureBuilder<{}, {}, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>;
}
createMyPlugin() {
// When creating a plugin for tRPC, you use the same API as creating any other tRPC-app
// this is the plugin's root `t`-object
const const t: TRPCRootObject<{}, {}, RuntimeConfigOptions<{}, {}>, {
ctx: {};
meta: {};
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC
.TRPCBuilder<object, object>.context<{}>(): TRPCBuilder<{}, object>context<{
// the procedure using the plugin will need to extend this context
}>()
.TRPCBuilder<{}, object>.meta<{}>(): TRPCBuilder<{}, {}>meta<{
// the base `initTRPC`-object of the application using this needs to extend this meta
}>()
.TRPCBuilder<{}, {}>.create<RuntimeConfigOptions<{}, {}>>(opts?: RuntimeConfigOptions<{}, {}> | undefined): TRPCRootObject<{}, {}, RuntimeConfigOptions<{}, {}>, {
ctx: {};
meta: {};
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
return {
// you can also add `.input()` if you want your plugin to do input validation
pluginProc: ProcedureBuilder<{}, {}, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
pluginProc: const t: TRPCRootObject<{}, {}, RuntimeConfigOptions<{}, {}>, {
ctx: {};
meta: {};
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<{}, {}, RuntimeConfigOptions<{}, {}>, { ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{}, {}, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure.ProcedureBuilder<{}, {}, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
fromPlugin: "hello from myPlugin";
}>(fn: MiddlewareBuilder<{}, {}, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker> | MiddlewareFunction<{}, {}, object, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker>): ProcedureBuilder<{}, {}, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use((opts: {
ctx: {};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: {} | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) => {
return opts: {
ctx: {};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: {} | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
fromPlugin: "hello from myPlugin";
}>(opts: {
ctx?: {
fromPlugin: "hello from myPlugin";
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
fromPlugin: "hello from myPlugin";
}>> (+2 overloads)
next({
ctx?: {
fromPlugin: "hello from myPlugin";
} | undefined
ctx: {
fromPlugin: "hello from myPlugin"fromPlugin: 'hello from myPlugin' as type const = "hello from myPlugin"const,
},
});
}),
};
}
// ------------------------------------
// 🚀🚀🚀 the app using the plugin 🚀🚀🚀
// @filename: src/app.ts
import { function createMyPlugin(): {
pluginProc: ProcedureBuilder<{}, {}, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>;
}
createMyPlugin } from './myPlugin';
import { const initTRPC: TRPCBuilder<object, object>initTRPC, class TRPCErrorTRPCError } from '@trpc/server';
// the app's root `t`-object
const const t: TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, {
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC
.TRPCBuilder<object, object>.context<{}>(): TRPCBuilder<{}, object>context<{
// ...
}>()
.TRPCBuilder<{}, object>.create<RuntimeConfigOptions<{}, object>>(opts?: RuntimeConfigOptions<{}, object> | undefined): TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, {
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
export const const publicProcedure: ProcedureBuilder<{}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure = const t: TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, {
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, { ctx: {}; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure;
export const const router: RouterBuilder<{
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
router = const t: TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, {
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, { ctx: {}; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder<{
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
router;
// initialize the plugin (a real-world example would likely take options here)
const const plugin: {
pluginProc: ProcedureBuilder<{}, {}, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>;
}
plugin = function createMyPlugin(): {
pluginProc: ProcedureBuilder<{}, {}, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>;
}
createMyPlugin();
// create a base procedure using the plugin
const const procedureWithPlugin: ProcedureBuilder<{}, object, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedureWithPlugin = const publicProcedure: ProcedureBuilder<{}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure
.ProcedureBuilder<{}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.concat<{}, {}, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, DefaultErrorShape>(builder: ProcedureBuilder<{}, {}, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>): ProcedureBuilder<{}, object, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
concat(
const plugin: {
pluginProc: ProcedureBuilder<{}, {}, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>;
}
plugin.pluginProc: ProcedureBuilder<{}, {}, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
pluginProc,
)
.ProcedureBuilder<{}, object, { fromPlugin: "hello from myPlugin"; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
fromPlugin: "hello from myPlugin";
}>(fn: MiddlewareBuilder<{
fromPlugin: "hello from myPlugin";
}, object, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker> | MiddlewareFunction<{}, object, {
fromPlugin: "hello from myPlugin";
}, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker>): ProcedureBuilder<{}, object, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(opts: {
ctx: {
fromPlugin: "hello from myPlugin";
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
fromPlugin: "hello from myPlugin";
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts => {
const { ctx } = opts: {
ctx: {
fromPlugin: "hello from myPlugin";
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
fromPlugin: "hello from myPlugin";
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts; return opts: {
ctx: {
fromPlugin: "hello from myPlugin";
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
fromPlugin: "hello from myPlugin";
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: () => Promise<MiddlewareResult<{
fromPlugin: "hello from myPlugin";
}>> (+2 overloads)
next()
})
export const const appRouter: BuiltRouter<{
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
hello: QueryProcedure<{
input: void;
output: "hello from myPlugin";
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
appRouter = const router: RouterBuilder
<{
hello: QueryProcedure<{
input: void;
output: "hello from myPlugin";
meta: object;
errorShape: DefaultErrorShape;
}>;
}>(_: {
hello: QueryProcedure<{
input: void;
output: "hello from myPlugin";
meta: object;
errorShape: DefaultErrorShape;
}>;
}) => BuiltRouter<{
ctx: {};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
hello: QueryProcedure<{
input: void;
output: "hello from myPlugin";
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
router({
hello: QueryProcedure<{
input: void;
output: "hello from myPlugin";
meta: object;
errorShape: DefaultErrorShape;
}>
hello: const procedureWithPlugin: ProcedureBuilder<{}, object, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedureWithPlugin.ProcedureBuilder<{}, object, { fromPlugin: "hello from myPlugin"; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<"hello from myPlugin">(resolver: ProcedureResolver<{}, object, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, "hello from myPlugin">): QueryProcedure<{
input: void;
output: "hello from myPlugin";
meta: object;
errorShape: DefaultErrorShape;
}>
query(opts: ProcedureResolverOptions<{}, object, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker>
opts => {
return opts: ProcedureResolverOptions<{}, object, {
fromPlugin: "hello from myPlugin";
}, UnsetMarker>
opts.ProcedureResolverOptions<{}, object, { fromPlugin: "hello from myPlugin"; }, UnsetMarker>.ctx: {
fromPlugin: "hello from myPlugin";
}
ctx.fromPlugin: "hello from myPlugin"fromPlugin;
})
})Extending middlewares
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.
const const fooMiddleware: MiddlewareBuilder<object, object, {
foo: "foo";
}, unknown>
fooMiddleware = const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.middleware: <{
foo: "foo";
}>(fn: MiddlewareFunction<object, object, object, {
foo: "foo";
}, unknown>) => MiddlewareBuilder<object, object, {
foo: "foo";
}, unknown>
middleware((opts: {
ctx: {};
type: ProcedureType;
path: string;
input: unknown;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) => {
return opts: {
ctx: {};
type: ProcedureType;
path: string;
input: unknown;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
foo: "foo";
}>(opts: {
ctx?: {
foo: "foo";
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
foo: "foo";
}>> (+2 overloads)
next({
ctx?: {
foo: "foo";
} | undefined
ctx: {
foo: "foo"foo: 'foo' as type const = "foo"const,
},
});
});
const const barMiddleware: MiddlewareBuilder<object, object, {
foo: "foo";
bar: "bar";
}, unknown>
barMiddleware = const fooMiddleware: MiddlewareBuilder<object, object, {
foo: "foo";
}, unknown>
fooMiddleware.MiddlewareBuilder<object, object, { foo: "foo"; }, unknown>.unstable_pipe<{
bar: "bar";
}>(fn: MiddlewareFunction<object, object, {
foo: "foo";
}, {
bar: "bar";
}, unknown> | MiddlewareBuilder<{
foo: "foo";
}, object, {
bar: "bar";
}, unknown>): MiddlewareBuilder<object, object, {
foo: "foo";
bar: "bar";
}, unknown>
unstable_pipe((opts: {
ctx: {
foo: "foo";
};
type: ProcedureType;
path: string;
input: unknown;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
foo: "foo";
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) => {
const { const ctx: {
foo: "foo";
}
ctx } = opts: {
ctx: {
foo: "foo";
};
type: ProcedureType;
path: string;
input: unknown;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
foo: "foo";
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts;
const ctx: {
foo: "foo";
}
ctx.foo; return opts: {
ctx: {
foo: "foo";
};
type: ProcedureType;
path: string;
input: unknown;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
foo: "foo";
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
bar: "bar";
}>(opts: {
ctx?: {
bar: "bar";
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
bar: "bar";
}>> (+2 overloads)
next({
ctx?: {
bar: "bar";
} | undefined
ctx: {
bar: "bar"bar: 'bar' as type const = "bar"const,
},
});
});
const const barProcedure: ProcedureBuilder<object, object, {
foo: "foo";
bar: "bar";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
barProcedure = const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
foo: "foo";
bar: "bar";
}>(fn: MiddlewareBuilder<{}, object, {
foo: "foo";
bar: "bar";
}, UnsetMarker> | MiddlewareFunction<object, object, object, {
foo: "foo";
bar: "bar";
}, UnsetMarker>): ProcedureBuilder<object, object, {
foo: "foo";
bar: "bar";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(const barMiddleware: MiddlewareBuilder<object, object, {
foo: "foo";
bar: "bar";
}, unknown>
barMiddleware);
const barProcedure: ProcedureBuilder<object, object, {
foo: "foo";
bar: "bar";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
barProcedure.ProcedureBuilder<object, object, { foo: "foo"; bar: "bar"; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<"bar">(resolver: ProcedureResolver<object, object, {
foo: "foo";
bar: "bar";
}, UnsetMarker, UnsetMarker, "bar">): QueryProcedure<{
input: void;
output: "bar";
meta: object;
errorShape: DefaultErrorShape;
}>
query((opts: ProcedureResolverOptions<object, object, {
foo: "foo";
bar: "bar";
}, UnsetMarker>
opts) => {
const { const ctx: {
foo: "foo";
bar: "bar";
}
ctx } = opts: ProcedureResolverOptions<object, object, {
foo: "foo";
bar: "bar";
}, UnsetMarker>
opts;
return ctx.bar: "bar"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.
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';
const const t: TRPCRootObject<{
a: {
b: "a";
};
}, object, RuntimeConfigOptions<{
a: {
b: "a";
};
}, object>, {
ctx: {
a: {
b: "a";
};
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC
.TRPCBuilder<object, object>.context<{
a: {
b: "a";
};
}>(): TRPCBuilder<{
a: {
b: "a";
};
}, object>
context<{
a: {
b: "a";
}
a: {
b: "a"b: 'a';
};
}>()
.TRPCBuilder<{ a: { b: "a"; }; }, object>.create<RuntimeConfigOptions<{
a: {
b: "a";
};
}, object>>(opts?: RuntimeConfigOptions<{
a: {
b: "a";
};
}, object> | undefined): TRPCRootObject<{
a: {
b: "a";
};
}, object, RuntimeConfigOptions<{
a: {
b: "a";
};
}, object>, {
ctx: {
a: {
b: "a";
};
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
const const fooMiddleware: MiddlewareBuilder<{
a: {
b: "a";
};
}, object, {
a: "a";
}, unknown>
fooMiddleware = const t: TRPCRootObject<{
a: {
b: "a";
};
}, object, RuntimeConfigOptions<{
a: {
b: "a";
};
}, object>, {
ctx: {
a: {
b: "a";
};
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<{ a: { b: "a"; }; }, object, RuntimeConfigOptions<{ a: { b: "a"; }; }, object>, { ctx: { a: { b: "a"; }; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.middleware: <{
a: "a";
}>(fn: MiddlewareFunction<{
a: {
b: "a";
};
}, object, object, {
a: "a";
}, unknown>) => MiddlewareBuilder<{
a: {
b: "a";
};
}, object, {
a: "a";
}, unknown>
middleware((opts: {
ctx: {
a: {
b: "a";
};
};
type: ProcedureType;
path: string;
input: unknown;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) => {
const { const ctx: {
a: {
b: "a";
};
}
ctx } = opts: {
ctx: {
a: {
b: "a";
};
};
type: ProcedureType;
path: string;
input: unknown;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts;
const ctx: {
a: {
b: "a";
};
}
ctx.a; // 👈 fooMiddleware expects `ctx.a` to be an object return opts: {
ctx: {
a: {
b: "a";
};
};
type: ProcedureType;
path: string;
input: unknown;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
a: "a";
}>(opts: {
ctx?: {
a: "a";
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
a: "a";
}>> (+2 overloads)
next({
ctx?: {
a: "a";
} | undefined
ctx: {
a: "a"a: 'a' as type const = "a"const, // 👈 `ctx.a` is no longer an object
},
});
});
const const barMiddleware: MiddlewareBuilder<{
a: {
b: "a";
};
}, object, {
foo: "foo";
}, unknown>
barMiddleware = const t: TRPCRootObject<{
a: {
b: "a";
};
}, object, RuntimeConfigOptions<{
a: {
b: "a";
};
}, object>, {
ctx: {
a: {
b: "a";
};
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<{ a: { b: "a"; }; }, object, RuntimeConfigOptions<{ a: { b: "a"; }; }, object>, { ctx: { a: { b: "a"; }; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.middleware: <{
foo: "foo";
}>(fn: MiddlewareFunction<{
a: {
b: "a";
};
}, object, object, {
foo: "foo";
}, unknown>) => MiddlewareBuilder<{
a: {
b: "a";
};
}, object, {
foo: "foo";
}, unknown>
middleware((opts: {
ctx: {
a: {
b: "a";
};
};
type: ProcedureType;
path: string;
input: unknown;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) => {
const { const ctx: {
a: {
b: "a";
};
}
ctx } = opts: {
ctx: {
a: {
b: "a";
};
};
type: ProcedureType;
path: string;
input: unknown;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts;
const ctx: {
a: {
b: "a";
};
}
ctx.a; // 👈 barMiddleware expects `ctx.a` to be an object return opts: {
ctx: {
a: {
b: "a";
};
};
type: ProcedureType;
path: string;
input: unknown;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
foo: "foo";
}>(opts: {
ctx?: {
foo: "foo";
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
foo: "foo";
}>> (+2 overloads)
next({
ctx?: {
foo: "foo";
} | undefined
ctx: {
foo: "foo"foo: 'foo' as type const = "foo"const,
},
});
});
// ❌ `ctx.a` does not overlap from `fooMiddleware` to `barMiddleware`
const fooMiddleware: MiddlewareBuilder<{
a: {
b: "a";
};
}, object, {
a: "a";
}, unknown>
fooMiddleware.MiddlewareBuilder<{ a: { b: "a"; }; }, object, { a: "a"; }, unknown>.unstable_pipe<{
foo: "foo";
}>(fn: MiddlewareFunction<{
a: {
b: "a";
};
}, object, {
a: "a";
}, {
foo: "foo";
}, unknown> | MiddlewareBuilder<{
a: "a";
}, object, {
foo: "foo";
}, unknown>): MiddlewareBuilder<{
a: {
b: "a";
};
}, object, {
a: "a";
foo: "foo";
}, unknown>
unstable_pipe(barMiddleware);
// ✅ `ctx.a` overlaps from `barMiddleware` and `fooMiddleware`
const barMiddleware: MiddlewareBuilder<{
a: {
b: "a";
};
}, object, {
foo: "foo";
}, unknown>
barMiddleware.MiddlewareBuilder<{ a: { b: "a"; }; }, object, { foo: "foo"; }, unknown>.unstable_pipe<{
a: "a";
}>(fn: MiddlewareFunction<{
a: {
b: "a";
};
}, object, {
foo: "foo";
}, {
a: "a";
}, unknown> | MiddlewareBuilder<{
a: {
b: "a";
};
foo: "foo";
}, object, {
a: "a";
}, unknown>): MiddlewareBuilder<{
a: {
b: "a";
};
}, object, {
a: "a";
foo: "foo";
}, unknown>
unstable_pipe(const fooMiddleware: MiddlewareBuilder<{
a: {
b: "a";
};
}, object, {
a: "a";
}, unknown>
fooMiddleware);Experimental: standalone middlewares
tRPC has an 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:
import {
const experimental_standaloneMiddleware: <TCtx extends {
ctx?: object;
meta?: object;
input?: unknown;
}>() => {
create: <$ContextOverrides>(fn: MiddlewareFunction<TCtx extends {
ctx: infer T extends object;
} ? T : any, TCtx extends {
meta: infer T_1 extends object;
} ? T_1 : object, object, $ContextOverrides, TCtx extends {
input: infer T_2;
} ? T_2 : unknown>) => MiddlewareBuilder<TCtx extends {
ctx: infer T extends object;
} ? T : any, TCtx extends {
meta: infer T_1 extends object;
} ? T_1 : object, $ContextOverrides, TCtx extends {
input: infer T_2;
} ? T_2 : unknown>;
}
experimental_standaloneMiddleware,
const initTRPC: TRPCBuilder<object, object>initTRPC,
class TRPCErrorTRPCError,
} from '@trpc/server';
import * as import zz from 'zod';
const const projectAccessMiddleware: MiddlewareBuilder<{
allowedProjects: string[];
}, object, object, {
projectId: string;
}>
projectAccessMiddleware = experimental_standaloneMiddleware<{
ctx: {
allowedProjects: string[];
};
input: {
projectId: string;
};
}>(): {
create: <$ContextOverrides>(fn: MiddlewareFunction<{
allowedProjects: string[];
}, object, object, $ContextOverrides, {
projectId: string;
}>) => MiddlewareBuilder<{
allowedProjects: string[];
}, object, $ContextOverrides, {
projectId: string;
}>;
}
experimental_standaloneMiddleware<{
ctx: {
allowedProjects: string[];
}
ctx: { allowedProjects: string[]allowedProjects: string[] }; // defaults to 'object' if not defined
input: {
projectId: string;
}
input: { projectId: stringprojectId: string }; // defaults to 'unknown' if not defined
// 'meta', not defined here, defaults to 'object | undefined'
}>().create: <object>(fn: MiddlewareFunction<{
allowedProjects: string[];
}, object, object, object, {
projectId: string;
}>) => MiddlewareBuilder<{
allowedProjects: string[];
}, object, object, {
projectId: string;
}>
create((opts: {
ctx: {
allowedProjects: string[];
};
type: ProcedureType;
path: string;
input: {
projectId: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) => {
if (!opts: {
ctx: {
allowedProjects: string[];
};
type: ProcedureType;
path: string;
input: {
projectId: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.ctx: {
allowedProjects: string[];
}
ctx.allowedProjects: string[]allowedProjects.Array<string>.includes(searchElement: string, fromIndex?: number): booleanincludes(opts: {
ctx: {
allowedProjects: string[];
};
type: ProcedureType;
path: string;
input: {
projectId: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.input: {
projectId: string;
}
input.projectId: stringprojectId)) {
throw new new TRPCError(opts: {
message?: string;
code: TRPC_ERROR_CODE_KEY;
cause?: unknown;
}): TRPCError
TRPCError({
code: "FORBIDDEN" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "UNAUTHORIZED" | "PAYMENT_REQUIRED" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'FORBIDDEN',
message?: string | undefinedmessage: 'Not allowed',
});
}
return opts: {
ctx: {
allowedProjects: string[];
};
type: ProcedureType;
path: string;
input: {
projectId: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: () => Promise<MiddlewareResult<object>> (+2 overloads)next();
});
const const t1: TRPCRootObject<{
allowedProjects: string[];
}, object, RuntimeConfigOptions<{
allowedProjects: string[];
}, object>, {
ctx: {
allowedProjects: string[];
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t1 = const initTRPC: TRPCBuilder<object, object>initTRPC
.TRPCBuilder<object, object>.context<{
allowedProjects: string[];
}>(): TRPCBuilder<{
allowedProjects: string[];
}, object>
context<{
allowedProjects: string[]allowedProjects: string[];
}>()
.TRPCBuilder<{ allowedProjects: string[]; }, object>.create<RuntimeConfigOptions<{
allowedProjects: string[];
}, object>>(opts?: RuntimeConfigOptions<{
allowedProjects: string[];
}, object> | undefined): TRPCRootObject<{
allowedProjects: string[];
}, object, RuntimeConfigOptions<{
allowedProjects: string[];
}, object>, {
ctx: {
allowedProjects: string[];
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
// ✅ `ctx.allowedProjects` satisfies "string[]" and `input.projectId` satisfies "string"
const const accessControlledProcedure: ProcedureBuilder<{
allowedProjects: string[];
}, object, {}, {
projectId: string;
}, {
projectId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
accessControlledProcedure = const t1: TRPCRootObject<{
allowedProjects: string[];
}, object, RuntimeConfigOptions<{
allowedProjects: string[];
}, object>, {
ctx: {
allowedProjects: string[];
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t1.TRPCRootObject<{ allowedProjects: string[]; }, object, RuntimeConfigOptions<{ allowedProjects: string[]; }, object>, { ctx: { allowedProjects: string[]; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{
allowedProjects: string[];
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedure
.ProcedureBuilder<{ allowedProjects: string[]; }, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
projectId: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
projectId: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<{
allowedProjects: string[];
}, object, object, {
projectId: string;
}, {
projectId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input(import zz.function object<{
projectId: z.ZodString;
}>(shape?: {
projectId: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
projectId: z.ZodString;
}, z.core.$strip>
object({ projectId: z.ZodStringprojectId: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.ProcedureBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }, { projectId: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<object>(fn: MiddlewareBuilder<{
allowedProjects: string[];
}, object, object, {
projectId: string;
}> | MiddlewareFunction<{
allowedProjects: string[];
}, object, object, object, {
projectId: string;
}>): ProcedureBuilder<{
allowedProjects: string[];
}, object, {}, {
projectId: string;
}, {
projectId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(const projectAccessMiddleware: MiddlewareBuilder<{
allowedProjects: string[];
}, object, object, {
projectId: string;
}>
projectAccessMiddleware);
// ❌ `ctx.allowedProjects` satisfies "string[]" but `input.projectId` does not satisfy "string"
const const accessControlledProcedure2: ProcedureBuilder<{
allowedProjects: string[];
}, object, {}, {
projectId: number;
}, {
projectId: number;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
accessControlledProcedure2 = const t1: TRPCRootObject<{
allowedProjects: string[];
}, object, RuntimeConfigOptions<{
allowedProjects: string[];
}, object>, {
ctx: {
allowedProjects: string[];
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t1.TRPCRootObject<{ allowedProjects: string[]; }, object, RuntimeConfigOptions<{ allowedProjects: string[]; }, object>, { ctx: { allowedProjects: string[]; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{
allowedProjects: string[];
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedure
.ProcedureBuilder<{ allowedProjects: string[]; }, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
projectId: z.ZodNumber;
}, z.core.$strip>>(schema: z.ZodObject<{
projectId: z.ZodNumber;
}, z.core.$strip>): ProcedureBuilder<{
allowedProjects: string[];
}, object, object, {
projectId: number;
}, {
projectId: number;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input(import zz.function object<{
projectId: z.ZodNumber;
}>(shape?: {
projectId: z.ZodNumber;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
projectId: z.ZodNumber;
}, z.core.$strip>
object({ projectId: z.ZodNumberprojectId: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber() }))
.ProcedureBuilder<{ allowedProjects: string[]; }, object, object, { projectId: number; }, { projectId: number; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<object>(fn: MiddlewareBuilder<{
allowedProjects: string[];
}, object, object, {
projectId: number;
}> | MiddlewareFunction<{
allowedProjects: string[];
}, object, object, object, {
projectId: number;
}>): ProcedureBuilder<{
allowedProjects: string[];
}, object, {}, {
projectId: number;
}, {
projectId: number;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(projectAccessMiddleware);
// ❌ `ctx.allowedProjects` does not satisfy "string[]" even though `input.projectId` satisfies "string"
const const t2: TRPCRootObject<{
allowedProjects: number[];
}, object, RuntimeConfigOptions<{
allowedProjects: number[];
}, object>, {
ctx: {
allowedProjects: number[];
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t2 = const initTRPC: TRPCBuilder<object, object>initTRPC
.TRPCBuilder<object, object>.context<{
allowedProjects: number[];
}>(): TRPCBuilder<{
allowedProjects: number[];
}, object>
context<{
allowedProjects: number[]allowedProjects: number[];
}>()
.TRPCBuilder<{ allowedProjects: number[]; }, object>.create<RuntimeConfigOptions<{
allowedProjects: number[];
}, object>>(opts?: RuntimeConfigOptions<{
allowedProjects: number[];
}, object> | undefined): TRPCRootObject<{
allowedProjects: number[];
}, object, RuntimeConfigOptions<{
allowedProjects: number[];
}, object>, {
ctx: {
allowedProjects: number[];
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
const const accessControlledProcedure3: ProcedureBuilder<{
allowedProjects: number[];
}, object, {}, {
projectId: string;
}, {
projectId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
accessControlledProcedure3 = const t2: TRPCRootObject<{
allowedProjects: number[];
}, object, RuntimeConfigOptions<{
allowedProjects: number[];
}, object>, {
ctx: {
allowedProjects: number[];
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t2.TRPCRootObject<{ allowedProjects: number[]; }, object, RuntimeConfigOptions<{ allowedProjects: number[]; }, object>, { ctx: { allowedProjects: number[]; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{
allowedProjects: number[];
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedure
.ProcedureBuilder<{ allowedProjects: number[]; }, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
projectId: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
projectId: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<{
allowedProjects: number[];
}, object, object, {
projectId: string;
}, {
projectId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input(import zz.function object<{
projectId: z.ZodString;
}>(shape?: {
projectId: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
projectId: z.ZodString;
}, z.core.$strip>
object({ projectId: z.ZodStringprojectId: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.ProcedureBuilder<{ allowedProjects: number[]; }, object, object, { projectId: string; }, { projectId: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<object>(fn: MiddlewareBuilder<{
allowedProjects: number[];
}, object, object, {
projectId: string;
}> | MiddlewareFunction<{
allowedProjects: number[];
}, object, object, object, {
projectId: string;
}>): ProcedureBuilder<{
allowedProjects: number[];
}, object, {}, {
projectId: string;
}, {
projectId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(projectAccessMiddleware);Here is an example with multiple standalone middlewares:
import { const experimental_standaloneMiddleware: <TCtx extends {
ctx?: object;
meta?: object;
input?: unknown;
}>() => {
create: <$ContextOverrides>(fn: MiddlewareFunction<TCtx extends {
ctx: infer T extends object;
} ? T : any, TCtx extends {
meta: infer T_1 extends object;
} ? T_1 : object, object, $ContextOverrides, TCtx extends {
input: infer T_2;
} ? T_2 : unknown>) => MiddlewareBuilder<TCtx extends {
ctx: infer T extends object;
} ? T : any, TCtx extends {
meta: infer T_1 extends object;
} ? T_1 : object, $ContextOverrides, TCtx extends {
input: infer T_2;
} ? T_2 : unknown>;
}
experimental_standaloneMiddleware, const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';
import * as import zz from 'zod';
const const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.create<RuntimeConfigOptions<object, object>>(opts?: RuntimeConfigOptions<object, object> | undefined): TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
const const schemaA: z.ZodObject<{
valueA: z.ZodString;
}, z.core.$strip>
schemaA = import zz.function object<{
valueA: z.ZodString;
}>(shape?: {
valueA: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
valueA: z.ZodString;
}, z.core.$strip>
object({ valueA: z.ZodStringvalueA: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() });
const const schemaB: z.ZodObject<{
valueB: z.ZodString;
}, z.core.$strip>
schemaB = import zz.function object<{
valueB: z.ZodString;
}>(shape?: {
valueB: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
valueB: z.ZodString;
}, z.core.$strip>
object({ valueB: z.ZodStringvalueB: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() });
const const valueAUppercaserMiddleware: MiddlewareBuilder<any, object, {
valueAUppercase: string;
}, {
valueA: string;
}>
valueAUppercaserMiddleware = experimental_standaloneMiddleware<{
input: z.infer<typeof schemaA>;
}>(): {
create: <$ContextOverrides>(fn: MiddlewareFunction<any, object, object, $ContextOverrides, {
valueA: string;
}>) => MiddlewareBuilder<any, object, $ContextOverrides, {
valueA: string;
}>;
}
experimental_standaloneMiddleware<{
input: {
valueA: string;
}
input: import zz.type infer<T> = T extends {
_zod: {
output: any;
};
} ? T["_zod"]["output"] : unknown
export infer
infer<typeof const schemaA: z.ZodObject<{
valueA: z.ZodString;
}, z.core.$strip>
schemaA>;
}>().create: <{
valueAUppercase: string;
}>(fn: MiddlewareFunction<any, object, object, {
valueAUppercase: string;
}, {
valueA: string;
}>) => MiddlewareBuilder<any, object, {
valueAUppercase: string;
}, {
valueA: string;
}>
create((opts: {
ctx: object | {};
type: ProcedureType;
path: string;
input: {
valueA: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) => {
return opts: {
ctx: object | {};
type: ProcedureType;
path: string;
input: {
valueA: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
valueAUppercase: string;
}>(opts: {
ctx?: {
valueAUppercase: string;
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
valueAUppercase: string;
}>> (+2 overloads)
next({
ctx?: {
valueAUppercase: string;
} | undefined
ctx: { valueAUppercase: stringvalueAUppercase: opts: {
ctx: object | {};
type: ProcedureType;
path: string;
input: {
valueA: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.input: {
valueA: string;
}
input.valueA: stringvalueA.String.toUpperCase(): stringtoUpperCase() },
});
});
const const valueBUppercaserMiddleware: MiddlewareBuilder<any, object, {
valueBUppercase: string;
}, {
valueB: string;
}>
valueBUppercaserMiddleware = experimental_standaloneMiddleware<{
input: z.infer<typeof schemaB>;
}>(): {
create: <$ContextOverrides>(fn: MiddlewareFunction<any, object, object, $ContextOverrides, {
valueB: string;
}>) => MiddlewareBuilder<any, object, $ContextOverrides, {
valueB: string;
}>;
}
experimental_standaloneMiddleware<{
input: {
valueB: string;
}
input: import zz.type infer<T> = T extends {
_zod: {
output: any;
};
} ? T["_zod"]["output"] : unknown
export infer
infer<typeof const schemaB: z.ZodObject<{
valueB: z.ZodString;
}, z.core.$strip>
schemaB>;
}>().create: <{
valueBUppercase: string;
}>(fn: MiddlewareFunction<any, object, object, {
valueBUppercase: string;
}, {
valueB: string;
}>) => MiddlewareBuilder<any, object, {
valueBUppercase: string;
}, {
valueB: string;
}>
create((opts: {
ctx: object | {};
type: ProcedureType;
path: string;
input: {
valueB: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) => {
return opts: {
ctx: object | {};
type: ProcedureType;
path: string;
input: {
valueB: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
valueBUppercase: string;
}>(opts: {
ctx?: {
valueBUppercase: string;
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
valueBUppercase: string;
}>> (+2 overloads)
next({
ctx?: {
valueBUppercase: string;
} | undefined
ctx: { valueBUppercase: stringvalueBUppercase: opts: {
ctx: object | {};
type: ProcedureType;
path: string;
input: {
valueB: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.input: {
valueB: string;
}
input.valueB: stringvalueB.String.toUpperCase(): stringtoUpperCase() },
});
});
const const combinedInputThatSatisfiesBothMiddlewares: z.ZodObject<{
valueA: z.ZodString;
valueB: z.ZodString;
extraProp: z.ZodString;
}, z.core.$strip>
combinedInputThatSatisfiesBothMiddlewares = import zz.function object<{
valueA: z.ZodString;
valueB: z.ZodString;
extraProp: z.ZodString;
}>(shape?: {
valueA: z.ZodString;
valueB: z.ZodString;
extraProp: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
valueA: z.ZodString;
valueB: z.ZodString;
extraProp: z.ZodString;
}, z.core.$strip>
object({
valueA: z.ZodStringvalueA: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(),
valueB: z.ZodStringvalueB: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(),
extraProp: z.ZodStringextraProp: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(),
});
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure
.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
valueA: z.ZodString;
valueB: z.ZodString;
extraProp: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
valueA: z.ZodString;
valueB: z.ZodString;
extraProp: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<object, object, object, {
valueA: string;
valueB: string;
extraProp: string;
}, {
valueA: string;
valueB: string;
extraProp: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input(const combinedInputThatSatisfiesBothMiddlewares: z.ZodObject<{
valueA: z.ZodString;
valueB: z.ZodString;
extraProp: z.ZodString;
}, z.core.$strip>
combinedInputThatSatisfiesBothMiddlewares)
.ProcedureBuilder<object, object, object, { valueA: string; valueB: string; extraProp: string; }, { valueA: string; valueB: string; extraProp: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
valueAUppercase: string;
}>(fn: MiddlewareBuilder<{}, object, {
valueAUppercase: string;
}, {
valueA: string;
valueB: string;
extraProp: string;
}> | MiddlewareFunction<object, object, object, {
valueAUppercase: string;
}, {
valueA: string;
valueB: string;
extraProp: string;
}>): ProcedureBuilder<object, object, {
valueAUppercase: string;
}, {
valueA: string;
valueB: string;
extraProp: string;
}, {
valueA: string;
valueB: string;
extraProp: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(const valueAUppercaserMiddleware: MiddlewareBuilder<any, object, {
valueAUppercase: string;
}, {
valueA: string;
}>
valueAUppercaserMiddleware)
.ProcedureBuilder<object, object, { valueAUppercase: string; }, { valueA: string; valueB: string; extraProp: string; }, { valueA: string; valueB: string; extraProp: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
valueBUppercase: string;
}>(fn: MiddlewareBuilder<{
valueAUppercase: string;
}, object, {
valueBUppercase: string;
}, {
valueA: string;
valueB: string;
extraProp: string;
}> | MiddlewareFunction<object, object, {
valueAUppercase: string;
}, {
valueBUppercase: string;
}, {
valueA: string;
valueB: string;
extraProp: string;
}>): ProcedureBuilder<object, object, {
valueAUppercase: string;
valueBUppercase: string;
}, {
valueA: string;
valueB: string;
extraProp: string;
}, {
valueA: string;
valueB: string;
extraProp: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(const valueBUppercaserMiddleware: MiddlewareBuilder<any, object, {
valueBUppercase: string;
}, {
valueB: string;
}>
valueBUppercaserMiddleware)
.ProcedureBuilder<object, object, { valueAUppercase: string; valueBUppercase: string; }, { valueA: string; valueB: string; extraProp: string; }, { valueA: string; valueB: string; extraProp: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<object, object, {
valueAUppercase: string;
valueBUppercase: string;
}, {
valueA: string;
valueB: string;
extraProp: string;
}, UnsetMarker, string>): QueryProcedure<{
input: {
valueA: string;
valueB: string;
extraProp: string;
};
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
query(
({
ProcedureResolverOptions<object, object, { valueAUppercase: string; valueBUppercase: string; }, { valueA: string; valueB: string; extraProp: string; }>.input: {
valueA: string;
valueB: string;
extraProp: string;
}
input: { valueA: stringvalueA, valueB: stringvalueB, extraProp: stringextraProp },
ProcedureResolverOptions<object, object, { valueAUppercase: string; valueBUppercase: string; }, { valueA: string; valueB: string; extraProp: string; }>.ctx: {
valueAUppercase: string;
valueBUppercase: string;
}
ctx: { valueAUppercase: stringvalueAUppercase, valueBUppercase: stringvalueBUppercase },
}) =>
`valueA: ${valueA: stringvalueA}, valueB: ${valueB: stringvalueB}, extraProp: ${extraProp: stringextraProp}, valueAUppercase: ${valueAUppercase: stringvalueAUppercase}, valueBUppercase: ${valueBUppercase: stringvalueBUppercase}`,
);