Procedure in oRPC
In oRPC, a procedure like a standard function but comes with built-in support for:
- Input/output validation
- Middleware
- Dependency injection
- Other extensibility features
Overview
Here’s an example of defining a procedure in oRPC:
import { os } from '@orpc/server'
const example = os
.use(aMiddleware) // Apply middleware
.input(z.object({ name: z.string() })) // Define input validation
.use(aMiddlewareWithInput, input => input.name) // Use middleware with typed input
.output(z.object({ id: z.number() })) // Define output validation
.handler(async ({ input, context }) => { // Define execution logic
return { id: 1 }
})
.callable() // Make the procedure callable like a regular function
.actionable() // Server Action compatibility
INFO
The .handler
method is the only required step. All other chains are optional.
Input/Output Validation
oRPC supports Zod, Valibot, Arktype, and any other Standard Schema library for input and output validation.
TIP
By explicitly specifying the .output
or your handler's return type
, you enable TypeScript to infer the output without parsing the handler's code. This approach can dramatically enhance both type-checking and IDE-suggestion speed.
type
Utility
For simple use-case without external libraries, use oRPC’s built-in type
utility. It takes a mapping function as its first argument:
import { os, type } from '@orpc/server'
const example = os
.input(type<{ value: number }>())
.output(type<{ value: number }, number>(({ value }) => value))
.handler(async ({ input }) => input)
Using Middleware
The .use
method allows you to pass middleware, which must call next
to continue execution.
const aMiddleware = os.middleware(async ({ context, next }) => next())
const example = os
.use(aMiddleware) // Apply middleware
.use(async ({ context, next }) => next()) // Inline middleware
.handler(async ({ context }) => { /* logic */ })
INFO
Middleware can be applied if the current context meets the middleware dependent context requirements and does not conflict with the current context.
Initial Configuration
Customize the initial input schema using .$input
:
const base = os.$input(z.void())
const base = os.$input<Schema<void, unknown>>()
Unlike .input
, the .$input
method lets you redefine the input schema after its initial configuration. This is useful when you need to enforce a void
input when no .input
is specified.
Reusability
Each modification to a builder creates a completely new instance, avoiding reference issues. This makes it easy to reuse and extend procedures efficiently.
const pub = os.use(logMiddleware) // Base setup for procedures that publish
const authed = pub.use(authMiddleware) // Extends 'pub' with authentication
const pubExample = pub.handler(async ({ context }) => { /* logic */ })
const authedExample = pubExample.use(authMiddleware)
This pattern helps prevent duplication while maintaining flexibility.