Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

Dedupe Middleware

Use context to prevent the same middleware from repeating expensive work.

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>>
$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>>
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>>
$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<...>>
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>
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<...>>
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>
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<...>>
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<...>
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>;
}): {
    ...;
}
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 6, 2026

Was this page helpful?