Skip to content

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:

ts
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:

ts
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.

ts
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:

ts
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.

ts
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.

Released under the MIT License.