Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

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 in router already use authProvider

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.
@see{@link https://orpc.dev/docs/procedure Procedure}
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.
@see{@link https://orpc.dev/docs/context#initial-context Context - Initial Context}
$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`.
@see{@link https://orpc.dev/docs/middleware Middleware}
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.
@see{@link https://orpc.dev/docs/procedure Procedure}
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.
@see{@link https://orpc.dev/docs/context#initial-context Context - Initial Context}
$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.
@see{@link https://orpc.dev/docs/middleware Middleware}
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.
@see{@link https://orpc.dev/docs/procedure 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.
@see{@link https://orpc.dev/docs/middleware Middleware}
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.
@see{@link https://orpc.dev/docs/procedure 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.
@example```ts const output = await call(getting, 'input') const output = await call(getting, 'input', { context: { db: 'postgres' } }) ```@see{@link https://orpc.dev/docs/client/server-side#one-off-calls Server-Side Clients - One-Off Calls}
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.
@see{@link https://orpc.dev/docs/middleware Middleware}
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.
@see{@link https://orpc.dev/docs/middleware Middleware}
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.
@see{@link https://orpc.dev/docs/router#extending-router Router - Extending 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
,
})

Last updated on August 7, 2026

Was this page helpful?