Dedupe Middleware
Learn how to use context to prevent the same middleware from repeating expensive work when it runs multiple times in a single call.
Problem
The same middleware can run more than once during a single call. This often happens when:
- a procedure calls another procedure that both use the same middleware
- you use
.use(authProvider).router(router), and some procedures inrouteralready useauthProvider
Solution
Store the computed value in context and reuse it when the middleware runs again.
For example, this middleware loads auth at most once per call:
const const authProvider: DecoratedMiddleware<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, unknown, any, Record<never, never>>
authProvider = const os: Builder<DefaultInitialContext & object, Record<never, never>>The oRPC procedure builder. Chain methods like `.input`, `.use`, and `.handler`
to define procedures, then compose them into routers.os
.Builder<DefaultInitialContext & object, Record<never, never>>.$context<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
}>(): Builder<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, Record<never, never>>
Declares the initial context type that must be provided when executing
procedures built from this builder.$context<{ headers: Headersheaders: Headers, auth?: {
id: string;
} | undefined
auth?: { id: stringid: string } | undefined, authLoaded?: boolean | undefinedauthLoaded?: boolean | undefined }>()
.Builder<{ headers: Headers; auth?: { id: string; } | undefined; authLoaded?: boolean | undefined; } & object, Record<never, never>>.middleware<{
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, unknown, any>(middleware: Middleware<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, unknown, any, Record<never, never>>): DecoratedMiddleware<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, unknown, any, Record<never, never>>
Creates a standalone middleware that can be composed and applied to any
compatible builder or procedure with `.use`.middleware(async ({ context: {
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object
context, next: MiddlewareNext<any>Invoke to continue the middleware chain.next }) => {
// reuse the loaded auth value if it was already loaded
const const auth: {
id: string;
} | undefined
auth = context: {
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object
context.authLoaded?: boolean | undefinedauthLoaded
? context: {
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object
context.auth?: {
id: string;
} | undefined
auth
: await function loadAuth(headers: Headers): Promise<{
id: string;
} | undefined>
loadAuth(context: {
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object
context.headers: Headersheaders)
return next: MiddlewareNext
<{
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}>(options: {
context: {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
};
}) => MiddlewareResult<{
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, any>
Invoke to continue the middleware chain.next({ context: {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}
context: { auth: {
id: string;
} | undefined
auth, authLoaded: booleanauthLoaded: true } })
})
You can now apply authProvider multiple times without loading auth again:
const const base: Builder<{
headers: Headers;
} & object, Record<never, never>>
base = const os: Builder<DefaultInitialContext & object, Record<never, never>>The oRPC procedure builder. Chain methods like `.input`, `.use`, and `.handler`
to define procedures, then compose them into routers.os.Builder<DefaultInitialContext & object, Record<never, never>>.$context<{
headers: Headers;
}>(): Builder<{
headers: Headers;
} & object, Record<never, never>>
Declares the initial context type that must be provided when executing
procedures built from this builder.$context<{ headers: Headersheaders: Headers }>()
const const foo: DecoratedProcedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, InitialInputSchema, Schema<string>, Record<never, never>, never>
foo = const base: Builder<{
headers: Headers;
} & object, Record<never, never>>
base.Builder<{ headers: Headers; } & object, Record<never, never>>.use<{
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, {
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, Record<never, never>>(middleware: Middleware<({
headers: Headers;
} & object) | ({
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object), {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, unknown, unknown, Record<never, never>>): BuilderWithMiddlewares<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, Record<...>>
Applies a middleware that runs before the handler of every procedure
built from this builder.use(const authProvider: DecoratedMiddleware<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, unknown, any, Record<never, never>>
authProvider).BuilderWithMiddlewares<{ headers: Headers; } & object & Omit<{ headers: Headers; auth?: { id: string; } | undefined; authLoaded?: boolean | undefined; } & object, "headers">, { auth: { ...; } | undefined; authLoaded: boolean; }, Record<...>>['handler']<string>(handler: ProcedureHandler<Omit<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, "auth" | "authLoaded"> & {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, unknown, string, ORPCErrorConstructorMap<Record<never, never>>>): DecoratedProcedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, ... 4 more ..., never>
Defines the function that implements the procedure and completes the
chain, returning a callable procedure.handler(({ context: Omit<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, "auth" | "authLoaded"> & {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}
context }) => 'Hello World')
const const bar: DecoratedProcedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, InitialInputSchema, Schema<string>, Record<never, never>, never>
bar = const base: Builder<{
headers: Headers;
} & object, Record<never, never>>
base.Builder<{ headers: Headers; } & object, Record<never, never>>.use<{
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, {
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, Record<never, never>>(middleware: Middleware<({
headers: Headers;
} & object) | ({
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object), {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, unknown, unknown, Record<never, never>>): BuilderWithMiddlewares<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, Record<...>>
Applies a middleware that runs before the handler of every procedure
built from this builder.use(const authProvider: DecoratedMiddleware<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, unknown, any, Record<never, never>>
authProvider).BuilderWithMiddlewares<{ headers: Headers; } & object & Omit<{ headers: Headers; auth?: { id: string; } | undefined; authLoaded?: boolean | undefined; } & object, "headers">, { auth: { ...; } | undefined; authLoaded: boolean; }, Record<...>>['handler']<string>(handler: ProcedureHandler<Omit<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, "auth" | "authLoaded"> & {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, unknown, string, ORPCErrorConstructorMap<Record<never, never>>>): DecoratedProcedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, ... 4 more ..., never>
Defines the function that implements the procedure and completes the
chain, returning a callable procedure.handler(({ context: Omit<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, "auth" | "authLoaded"> & {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}
context }) => {
// Reuse the auth value that is already stored in context.
return call<Omit<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, "auth" | "authLoaded"> & {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, InitialInputSchema, Schema<string>, Record<never, never>, never>(lazyableProcedure: Lazyable<Procedure<Omit<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, "auth" | "authLoaded"> & {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, ... 4 more ..., never>>, input: void, options: CallOptions<...>): PromiseWithError<...>
Quickly call a procedure without creating a client.call(const foo: DecoratedProcedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, InitialInputSchema, Schema<string>, Record<never, never>, never>
foo, var undefinedundefined, { context: Value<Promisable<Omit<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, "auth" | "authLoaded"> & {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}>, [clientContext: object]>
context })
})
// Applying authProvider again does not load auth a second time.
const const router: {
foo: Procedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, InitialInputSchema, Schema<string>, Record<never, never>, never>;
bar: Procedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, InitialInputSchema, Schema<string>, Record<never, never>, never>;
}
router = const base: Builder<{
headers: Headers;
} & object, Record<never, never>>
base
.Builder<{ headers: Headers; } & object, Record<never, never>>.use<{
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, {
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, Record<never, never>>(middleware: Middleware<({
headers: Headers;
} & object) | ({
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object), {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, unknown, unknown, Record<never, never>>): BuilderWithMiddlewares<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, Record<...>>
Applies a middleware that runs before the handler of every procedure
built from this builder.use(const authProvider: DecoratedMiddleware<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, unknown, any, Record<never, never>>
authProvider)
.BuilderWithMiddlewares<{ headers: Headers; } & object & Omit<{ headers: Headers; auth?: { id: string; } | undefined; authLoaded?: boolean | undefined; } & object, "headers">, { auth: { ...; } | undefined; authLoaded: boolean; }, Record<...>>['use']<object, Omit<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, "auth" | "authLoaded"> & {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, Record<never, never>>(middleware: Middleware<Omit<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, "auth" | "authLoaded"> & {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, object, unknown, unknown, Record<...>>): BuilderWithMiddlewares<...>
Applies a middleware that runs before the handler of every procedure
built from this builder.use(({ next: MiddlewareNext<unknown>Invoke to continue the middleware chain.next }) => {
// Additional middleware logic
return next: MiddlewareNext
<object>(options?: {
context?: object | undefined;
} | undefined) => MiddlewareResult<object, unknown>
Invoke to continue the middleware chain.next()
})
.BuilderWithMiddlewares<{ headers: Headers; } & object & Omit<{ headers: Headers; auth?: { id: string; } | undefined; authLoaded?: boolean | undefined; } & object, "headers">, { auth: { ...; } | undefined; authLoaded: boolean; }, Record<...>>['router']<{
foo: DecoratedProcedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, InitialInputSchema, Schema<string>, Record<never, never>, never>;
bar: DecoratedProcedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, InitialInputSchema, Schema<...>, Record<...>, never>;
}>(router: {
foo: DecoratedProcedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, InitialInputSchema, Schema<string>, Record<never, never>, never>;
bar: DecoratedProcedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, InitialInputSchema, Schema<...>, Record<...>, never>;
}): {
...;
}
Applies the builder's middleware, errors, and metadata to every procedure
in the given router.router({
foo: DecoratedProcedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, InitialInputSchema, Schema<string>, Record<never, never>, never>
foo,
bar: DecoratedProcedure<{
headers: Headers;
} & object & Omit<{
headers: Headers;
auth?: {
id: string;
} | undefined;
authLoaded?: boolean | undefined;
} & object, "headers">, {
auth: {
id: string;
} | undefined;
authLoaded: boolean;
}, InitialInputSchema, Schema<string>, Record<never, never>, never>
bar,
})