Middleware
Run code before and after oRPC handlers with composable middleware that can inject context, guard access, and modify input or output.
Overview
import { 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 } from '@orpc/server'
const const example: DecoratedMiddleware<{
something?: string;
} & object, {
user: {
id: number;
name: string;
};
}, unknown, any, {
RATE_LIMITED: {};
}>
example = 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<{
something?: string;
}>(): Builder<{
something?: string;
} & object, Record<never, never>>
Declares the initial context type that must be provided when executing
procedures built from this builder.$context<{ something?: string | undefinedsomething?: string }>() // <- define initial context
.Builder<{ something?: string; } & object, Record<never, never>>.meta(...plugins: MetaPlugin<InitialInputSchema, InitialOutputSchema, Record<never, never>>[]): Builder<{
something?: string;
} & object, Record<never, never>>
Applies metadata plugins to procedures built from this builder.meta(const someMeta: AnyMetaPluginsomeMeta) // <- attach metadata
.Builder<{ something?: string; } & object, Record<never, never>>.errors<{
RATE_LIMITED: {};
}>(errors: {
RATE_LIMITED: {};
}): Builder<{
something?: string;
} & object, {
RATE_LIMITED: {};
}>
Defines typesafe errors that procedures built from this builder can throw
via the `errors` utility in handlers and middleware.errors({ type RATE_LIMITED: {}RATE_LIMITED: {} }) // <- attach errors
.Builder<{ something?: string; } & object, { RATE_LIMITED: {}; }>.middleware<{
user: {
id: number;
name: string;
};
}, unknown, any>(middleware: Middleware<{
something?: string;
} & object, {
user: {
id: number;
name: string;
};
}, unknown, any, {
RATE_LIMITED: {};
}>): DecoratedMiddleware<{
something?: string;
} & object, {
user: {
id: number;
name: string;
};
}, unknown, any, {
RATE_LIMITED: {};
}>
Creates a standalone middleware that can be composed and applied to any
compatible builder or procedure with `.use`.middleware(async ({ context: {
something?: string;
} & object
context, next: MiddlewareNext<any>Invoke to continue the middleware chain.next, errors: ORPCErrorConstructorMap<{
RATE_LIMITED: {};
}>
errors }) => { // <- middleware logic
try {
// `await` is required to catch async errors
return await next: MiddlewareNext
<{
user: {
id: number;
name: string;
};
}>(options: {
context: {
user: {
id: number;
name: string;
};
};
}) => MiddlewareResult<{
user: {
id: number;
name: string;
};
}, any>
Invoke to continue the middleware chain.next({
context: {
user: {
id: number;
name: string;
};
}
context: { // <- Inject additional context
user: {
id: number;
name: string;
}
user: { id: numberid: 1, name: stringname: 'John' }
}
})
}
catch (function (local var) error: unknownerror) {
var console: Consoleconsole.Console.error(...data: any[]): voidThe **`console.error()`** static method outputs a message to the console at the "error" log level. The message is only displayed to the user if the console is configured to display error output. In most cases, the log level is configured within the console UI. The message may be formatted as an error, with red colors and call stack information.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static)error(function (local var) error: unknownerror)
throw function (local var) error: unknownerror
}
finally {
// Cleanup logic after execution
}
})
Initial Context
Use .$context to declare the initial context required when middleware is applied.
Learn more in the Context Documentation.
Metadata
Use .meta to attach metadata to middleware. This metadata is applied to any procedures that use the middleware. Learn more in the Metadata documentation.
Typesafe Errors
Use .errors to attach error definitions to middleware. These errors are available in the middleware and any procedures that use it. Learn more in the Typesafe Error Handling documentation.
Middleware Context
Middleware can be used to inject or guard the context.
const const setting: DecoratedProcedure<DefaultInitialContext & object, Omit<{
auth: {
userId: number;
} | null;
}, "auth"> & {
auth: {
userId: number;
};
}, InitialInputSchema, Schema<void>, Record<never, never>, never>
setting = 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>>.use<{
auth: {
userId: number;
} | null;
}, DefaultInitialContext & object, Record<never, never>>(middleware: Middleware<DefaultInitialContext & object, {
auth: {
userId: number;
} | null;
}, unknown, unknown, Record<never, never>>): BuilderWithMiddlewares<DefaultInitialContext & object, {
auth: {
userId: number;
} | null;
}, Record<never, never>>
Applies a middleware that runs before the handler of every procedure
built from this builder.use(async ({ context: DefaultInitialContext & objectcontext, next: MiddlewareNext<unknown>Invoke to continue the middleware chain.next }) => {
return next: MiddlewareNext
<{
auth: {
userId: number;
} | null;
}>(options: {
context: {
auth: {
userId: number;
} | null;
};
}) => MiddlewareResult<{
auth: {
userId: number;
} | null;
}, unknown>
Invoke to continue the middleware chain.next({
context: {
auth: {
userId: number;
} | null;
}
context: {
auth: {
userId: number;
} | null
auth: await function auth(): {
userId: number;
} | null
auth() // <- inject auth
}
})
})
.BuilderWithMiddlewares<DefaultInitialContext & object, { auth: { userId: number; } | null; }, Record<never, never>>['use']<{
auth: {
userId: number;
};
}, Omit<DefaultInitialContext & object, "auth"> & {
auth: {
userId: number;
} | null;
}, Record<never, never>>(middleware: Middleware<Omit<DefaultInitialContext & object, "auth"> & {
auth: {
userId: number;
} | null;
}, {
auth: {
userId: number;
};
}, unknown, unknown, Record<never, never>>): BuilderWithMiddlewares<DefaultInitialContext & object, Omit<{
auth: {
userId: number;
} | null;
}, "auth"> & {
auth: {
userId: number;
};
}, Record<...>>
Applies a middleware that runs before the handler of every procedure
built from this builder.use(async ({ context: Omit<DefaultInitialContext & object, "auth"> & {
auth: {
userId: number;
} | null;
}
context, next: MiddlewareNext<unknown>Invoke to continue the middleware chain.next }) => {
if (!context: Omit<DefaultInitialContext & object, "auth"> & {
auth: {
userId: number;
} | null;
}
context.auth: {
userId: number;
} | null
auth) { // <- guard auth
throw new new ORPCError<"UNAUTHORIZED", unknown>(code: "UNAUTHORIZED", options?: ORPCErrorOptions<unknown> | undefined): ORPCError<"UNAUTHORIZED", unknown>Typed error carrying a `code`, a `message`, and optional `data`.
Throw it from handlers or middleware to produce typed error responses on the client.ORPCError('UNAUTHORIZED')
}
return next: MiddlewareNext
<{
auth: {
userId: number;
};
}>(options: {
context: {
auth: {
userId: number;
};
};
}) => MiddlewareResult<{
auth: {
userId: number;
};
}, unknown>
Invoke to continue the middleware chain.next({
context: {
auth: {
userId: number;
};
}
context: {
auth: {
userId: number;
}
auth: context: Omit<DefaultInitialContext & object, "auth"> & {
auth: {
userId: number;
} | null;
}
context.auth: {
userId: number;
}
auth // <- override auth (now guaranteed to be non-null)
}
})
})
.BuilderWithMiddlewares<DefaultInitialContext & object, Omit<{ auth: { userId: number; } | null; }, "auth"> & { auth: { userId: number; }; }, Record<never, never>>['handler']<void>(handler: ProcedureHandler<Omit<DefaultInitialContext & object, "auth"> & Omit<{
auth: {
userId: number;
} | null;
}, "auth"> & {
auth: {
userId: number;
};
}, unknown, void, ORPCErrorConstructorMap<Record<never, never>>>): DecoratedProcedure<DefaultInitialContext & object, Omit<{
auth: {
userId: number;
} | null;
}, "auth"> & {
auth: {
userId: number;
};
}, InitialInputSchema, Schema<void>, Record<never, never>, never>
Defines the function that implements the procedure and completes the
chain, returning a callable procedure.handler(async ({ context: Omit<DefaultInitialContext & object, "auth"> & Omit<{
auth: {
userId: number;
} | null;
}, "auth"> & {
auth: {
userId: number;
};
}
context }) => {
var console: Consoleconsole.Console.log(...data: any[]): voidThe **`console.log()`** static method outputs a message to the console.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(context: Omit<DefaultInitialContext & object, "auth"> & Omit<{
auth: {
userId: number;
} | null;
}, "auth"> & {
auth: {
userId: number;
};
}
context.auth: {
userId: number;
}
auth) // <- auth is guaranteed to be non-null here
})
Middleware Input
Middleware can access input in type-safe manner, enabling use cases like permission checks.
const canUpdate = os.middleware(async ({ context, next }, input: number) => {
// Perform permission check
return next()
})
const ping = os
.input(z.number())
.use(canUpdate) // <- input already matches middleware's expected shape
.handler(async ({ input }) => {
// Handler logic
})
const pong = os
.input(z.object({ id: z.number() }))
.use(canUpdate.adaptInput(input => input.id)) // <- adapt input to match middleware's expected shape
.handler(async ({ input }) => {
// Handler logic
})
Middleware Output
Middleware can also modify the output of a handler, such as implementing caching mechanisms.
const cache = os.middleware(async ({ context, next, path }, input, done) => {
const cacheKey = path.join('/') + JSON.stringify(input)
if (db.has(cacheKey)) {
return done({ output: db.get(cacheKey) })
}
const result = await next({})
db.set(cacheKey, result.output)
return result
})
Inline Middleware
Middleware is simply a function that can be defined inline with .use, which is useful for simple middleware cases.
const example = os
.use(async ({ context, next }) => {
// Execute logic before the handler
return next()
})
.handler(async ({ context }) => {
// Handler logic
})
Combining Middleware
Multiple middleware functions can be combined using .use.
const mergedMiddleware = aMiddleware
.use(async ({ next }) => next())
.use(anotherMiddleware)