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() // Like .callable, but compatible with server actions
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 */ })
To learn more, see the Middleware documentation.
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.