Router
A router is a plain, nestable object made up of procedures. Routers can also modify those procedures, which makes it easy to organize and extend your API.
Overview
Define a router as a plain JavaScript object where each key maps to a procedure:
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 ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>ping = 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>>.handler<string>(handler: ProcedureHandler<DefaultInitialContext & object, unknown, string, ORPCErrorConstructorMap<Record<never, never>>>): DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>handler(async () => 'ping')
const const pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>pong = 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>>.handler<string>(handler: ProcedureHandler<DefaultInitialContext & object, unknown, string, ORPCErrorConstructorMap<Record<never, never>>>): DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>handler(async () => 'pong')
export const const router: {
ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
nested: {
ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<...>, Record<...>, never>;
pong: DecoratedProcedure<...>;
};
}
router = {
ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>ping,
pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>pong,
nested: {
ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>;
}
nested: { ping: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>ping, pong: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>pong }
}
Extending Router
You can extend a router with shared behavior. For example, by applying authentication middleware or attaching metadata to every procedure:
const router = os.use(requiredAuth).meta(requireAuthMeta).router({
ping,
pong,
nested: {
ping,
pong,
}
})
Lazy Router
Routers can also be lazy-loaded. This is useful for code splitting and can improve cold start performance by deferring route initialization until it is needed.
const router = {
ping,
pong,
planet: os.lazy(() => import('./planet'))
}const PlanetSchema = z.object({
id: z.number().int().min(1),
name: z.string(),
description: z.string().optional(),
})
export const listPlanet = os
.input(
z.object({
limit: z.number().int().min(1).max(100).optional(),
cursor: z.number().int().min(0).default(0),
}),
)
.handler(async ({ input }) => {
// your list code here
return [{ id: 1, name: 'name' }]
})
export default {
list: listPlanet,
// ...
}Utilities
Infer Router Inputs
Infers the input type for each procedure in the router.
import type { type InferRouterInputs<T extends AnyRouter> = T extends Procedure<any, any, infer UInputSchema extends AnySchema, any, any, any> ? InferSchemaInput<UInputSchema> : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterInputs<U> : never; }Infer all router inputs.InferRouterInputs } from '@orpc/server'
export type type Inputs = {
planet: {
list: {
limit?: number | undefined;
cursor?: number | undefined;
};
find: {
id: number;
};
create: {
name: string;
description?: string | undefined;
};
};
}
Inputs = type InferRouterInputs<T extends AnyRouter> = T extends Procedure<any, any, infer UInputSchema extends AnySchema, any, any, any> ? InferSchemaInput<UInputSchema> : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterInputs<U> : never; }Infer all router inputs.InferRouterInputs<typeof const router: {
planet: {
list: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>;
find: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
id: ZodNumber;
}, $strip>, ZodObject<...>, object>;
create: ImplementedProcedure<...>;
};
}
router>
type type FindPlanetInput = {
id: number;
}
FindPlanetInput = type Inputs = {
planet: {
list: {
limit?: number | undefined;
cursor?: number | undefined;
};
find: {
id: number;
};
create: {
name: string;
description?: string | undefined;
};
};
}
Inputs['planet']['find']
Infer Router Outputs
Infers the output type for each procedure in the router.
import type { type InferRouterOutputs<T extends AnyRouter> = T extends Procedure<any, any, any, infer UOutputSchema extends AnySchema, any, any> ? InferSchemaOutput<UOutputSchema> : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterOutputs<U> : never; }Infer all router outputs.InferRouterOutputs } from '@orpc/server'
export type type Outputs = {
planet: {
list: {
id: number;
name: string;
description?: string | undefined;
}[];
find: {
id: number;
name: string;
description?: string | undefined;
};
create: {
id: number;
name: string;
description?: string | undefined;
};
};
}
Outputs = type InferRouterOutputs<T extends AnyRouter> = T extends Procedure<any, any, any, infer UOutputSchema extends AnySchema, any, any> ? InferSchemaOutput<UOutputSchema> : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterOutputs<U> : never; }Infer all router outputs.InferRouterOutputs<typeof const router: {
planet: {
list: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>;
find: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
id: ZodNumber;
}, $strip>, ZodObject<...>, object>;
create: ImplementedProcedure<...>;
};
}
router>
type type FindPlanetOutput = {
id: number;
name: string;
description?: string | undefined;
}
FindPlanetOutput = type Outputs = {
planet: {
list: {
id: number;
name: string;
description?: string | undefined;
}[];
find: {
id: number;
name: string;
description?: string | undefined;
};
create: {
id: number;
name: string;
description?: string | undefined;
};
};
}
Outputs['planet']['find']
Infer Router Initial Contexts
Infers the initial context for each procedure in the router.
import type { type InferRouterInitialContexts<T extends AnyRouter> = T extends Procedure<infer UInitialContext extends Context, any, any, any, any, any> ? UInitialContext : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterInitialContexts<U> : never; }Infer all initial context of the router.InferRouterInitialContexts } from '@orpc/server'
export type type InitialContexts = {
planet: {
list: {
headers?: IncomingHttpHeaders;
} & object;
find: {
headers?: IncomingHttpHeaders;
} & object;
create: {
headers?: IncomingHttpHeaders;
} & object;
};
}
InitialContexts = type InferRouterInitialContexts<T extends AnyRouter> = T extends Procedure<infer UInitialContext extends Context, any, any, any, any, any> ? UInitialContext : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterInitialContexts<U> : never; }Infer all initial context of the router.InferRouterInitialContexts<typeof const router: {
planet: {
list: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>;
find: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
id: ZodNumber;
}, $strip>, ZodObject<...>, object>;
create: ImplementedProcedure<...>;
};
}
router>
type type FindPlanetInitialContext = {
headers?: IncomingHttpHeaders;
} & object
FindPlanetInitialContext = type InitialContexts = {
planet: {
list: {
headers?: IncomingHttpHeaders;
} & object;
find: {
headers?: IncomingHttpHeaders;
} & object;
create: {
headers?: IncomingHttpHeaders;
} & object;
};
}
InitialContexts['planet']['find']
Infer Router Final Contexts
Infers the final context for each procedure in the router by combining the initial and injected context. This is the closest match to the context the procedure’s handler receives.
import type { type InferRouterFinalContexts<T extends AnyRouter> = T extends Procedure<infer UInitialContext extends Context, infer UInjectedContext extends Context, any, any, any, any> ? MergedContext<UInitialContext, UInjectedContext> : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterFinalContexts<U> : never; }Infer all current context of the router.InferRouterFinalContexts } from '@orpc/server'
export type type FinalContexts = {
planet: {
list: {
headers?: IncomingHttpHeaders;
} & object;
find: {
headers?: IncomingHttpHeaders;
} & object;
create: {
headers?: IncomingHttpHeaders;
} & object;
};
}
FinalContexts = type InferRouterFinalContexts<T extends AnyRouter> = T extends Procedure<infer UInitialContext extends Context, infer UInjectedContext extends Context, any, any, any, any> ? MergedContext<UInitialContext, UInjectedContext> : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterFinalContexts<U> : never; }Infer all current context of the router.InferRouterFinalContexts<typeof const router: {
planet: {
list: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>;
find: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
id: ZodNumber;
}, $strip>, ZodObject<...>, object>;
create: ImplementedProcedure<...>;
};
}
router>
type type FindPlanetFinalContext = {
headers?: IncomingHttpHeaders;
} & object
FindPlanetFinalContext = type FinalContexts = {
planet: {
list: {
headers?: IncomingHttpHeaders;
} & object;
find: {
headers?: IncomingHttpHeaders;
} & object;
create: {
headers?: IncomingHttpHeaders;
} & object;
};
}
FinalContexts['planet']['find']
Infer Router Errors
Infers the throwable errors each procedure in a router can produce.
import type { type InferRouterErrors<T extends AnyRouter> = T extends Procedure<any, any, any, any, infer UErrorMap extends ErrorMap, infer UReturnedError extends AnyORPCError> ? UReturnedError | ORPCErrorFromErrorMap<UErrorMap> | Error : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterErrors<U> : never; }Infer throwable errors for each procedure, preserving the router shape.InferRouterErrors } from '@orpc/server'
export type type Errors = {
planet: {
list: Error;
find: Error;
create: Error;
};
}
Errors = type InferRouterErrors<T extends AnyRouter> = T extends Procedure<any, any, any, any, infer UErrorMap extends ErrorMap, infer UReturnedError extends AnyORPCError> ? UReturnedError | ORPCErrorFromErrorMap<UErrorMap> | Error : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterErrors<U> : never; }Infer throwable errors for each procedure, preserving the router shape.InferRouterErrors<typeof const router: {
planet: {
list: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>;
find: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
id: ZodNumber;
}, $strip>, ZodObject<...>, object>;
create: ImplementedProcedure<...>;
};
}
router>
type type FindPlanetError = ErrorFindPlanetError = type Errors = {
planet: {
list: Error;
find: Error;
create: Error;
};
}
Errors['planet']['find']
Infer Router Error
Infers all possible throwable errors the entire router can produce. This is useful when you want a single type for router-wide error handling.
import type { type InferRouterError<T extends AnyRouter> = T extends Procedure<any, any, any, any, infer UErrorMap extends ErrorMap, infer UReturnedError extends AnyORPCError> ? UReturnedError | ORPCErrorFromErrorMap<UErrorMap> | Error : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterError<U> : never; }[keyof T]Infer the union of throwable errors for entire router.InferRouterError } from '@orpc/server'
export type type RouterError = ErrorRouterError = type InferRouterError<T extends AnyRouter> = T extends Procedure<any, any, any, any, infer UErrorMap extends ErrorMap, infer UReturnedError extends AnyORPCError> ? UReturnedError | ORPCErrorFromErrorMap<UErrorMap> | Error : { [K in keyof T]: T[K] extends Lazyable<infer U extends AnyRouter> ? InferRouterError<U> : never; }[keyof T]Infer the union of throwable errors for entire router.InferRouterError<typeof const router: {
planet: {
list: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>;
find: ImplementedProcedure<{
headers?: IncomingHttpHeaders;
} & object, object, ZodObject<{
id: ZodNumber;
}, $strip>, ZodObject<...>, object>;
create: ImplementedProcedure<...>;
};
}
router>