oRPC Documentation
repository·main·Indexed 26 days ago
https://github.com/middleapi/orpcA TypeScript framework for building typesafe APIs using a contract-first approach. oRPC provides end-to-end type safety for inputs, outputs, and errors across various runtimes (Node.js, Bun, Deno, Cloudflare) and frameworks. It features first-class OpenAPI support, integrations with schema validators like Zod, Valibot, and ArkType, and compatibility with TanStack Query, SWR, and Pinia Colada. The ecosystem includes specialized packages for Pub/Sub, rate limiting, and observability via OpenTelemetry.
What's inside oRPC
- Mini oRPC is a simplified implementation of oRPC designed for educational purposes. It includes essential features to help developers understand the core concepts of oRPC in a straightforward and easy-to-follow manner. It serves as an ideal starting point for learning the architecture and patterns used in the full oRPC ecosystem.
Overview of oRPC core packages
mainoRPC is a framework for building typesafe APIs. The core workflow involves defining an API contract and then implementing it on a server and consuming it with a client to achieve end-to-end type safety.
Core Packages:
@orpc/contract: Use this to define your API contract as the single source of truth.@orpc/server: Use this to build APIs or implement the defined contracts.@orpc/client: Use this to consume APIs with full end-to-end type safety.@orpc/openapi: Use this to add OpenAPI compatibility to your APIs.
Overview of oRPC features and capabilities
mainoRPC is a TypeScript library for building type-safe APIs. It provides end-to-end type safety for inputs, outputs, and errors. Key features include:
- OpenAPI Support: Built-in, first-class support for generating OpenAPI specifications.
- Contract-First Development: Support for defining API contracts before implementation.
- Framework Integrations: Works with TanStack Query (React, Vue, Solid, Svelte) and Pinia Colada.
- Server Action Compatibility: Fully compatible with React Server Actions (Next.js, TanStack Start).
- Schema Support: Works with Zod, Valibot, ArkType, and other standard schema validators.
- Native Type Support: Handles
Date,File,Blob,BigInt,URL, and more. - Multi-Runtime: Optimized for Cloudflare, Deno, Bun, and Node.js.
- Advanced Capabilities: Supports SSE (Server-Sent Events), streaming, lazy routing, and a metadata system for extensions.
Understand the oRPC Protocol and Serializer
mainThe oRPC protocol is a lightweight protocol for remote procedure calls that supports more native types than plain JSON. It uses a serializer to handle complex types includingDate,BigInt,RegExp,URL,Set,Map,Blob,File,AsyncIteratorObject, andReadableStream<Uint8Array>.Understand the oRPC Architecture
mainoRPC is structured into several core architectural layers that allow for different development workflows:
- Procedure Builder/Caller: The core lightweight component used to define and call procedures within the same environment.
- Contract First: A workflow that separates contract definitions from implementation, allowing for decoupled development.
- Standard Server: An abstraction layer that enables oRPC adapters to run on various runtimes (Cloudflare Workers, Node.js, Bun, Deno, etc.) without runtime-specific code.
- RPC Handler/Link: Implements the oRPC RPC Protocol for efficient remote procedure invocation, supporting native types like
Date,Map,Set, andBigInt. - OpenAPI Support: Provides handlers, links, and generators to implement standards-compliant remote procedure invocation following the OpenAPI Specification.
Understand Standard JSON Schema support
mainoRPC uses Standard JSON Schema to enable compatibility with tools like the OpenAPI Generator and Smart Coercion. If a schema library implements this specification, these tools will work automatically. If a library does not implement it, oRPC will treat the schema as unknown and default to an empty JSON schema.Setup OpenTelemetry instrumentation
mainUse
ORPCInstrumentationto automatically instrument both the oRPC client and server. This enables distributed tracing across your application.// Server-side setup import { NodeSDK } from '@opentelemetry/sdk-node' import { ORPCInstrumentation } from '@orpc/opentelemetry' const sdk = new NodeSDK({ instrumentations: [ new ORPCInstrumentation(), ], }) sdk.start() // Client-side setup import { WebTracerProvider } from '@opentelemetry/sdk-trace-web' import { registerInstrumentations } from '@opentelemetry/instrumentation' import { ORPCInstrumentation } from '@orpc/opentelemetry' const provider = new WebTracerProvider() provider.register() registerInstrumentations({ instrumentations: [ new ORPCInstrumentation(), ], })Organize oRPC Monorepo Structures
mainoRPC supports three primary architectural patterns for monorepos. The recommended approach is to separate the server component (with
compositeenabled) into a dedicated package.Best Practices:
- Use linked workspace packages (e.g., PNPM Workspace protocol) instead of alias imports inside server components.
- In
/apps, usereferencesintsconfig.jsonto depend on packages. - In
/packages, enablecomposite: trueintsconfig.json.
apps/ ├─ api/ // Import `core-contract` and implement it ├─ web/ // Import `core-contract` and set up @orpc/client here ├─ app/ packages/ ├─ core-contract/ // Define contract with @orpc/contract ├─ .../apps/ ├─ api/ // Import `core-service` and run it in your environment ├─ web/ // Import `core-service` and set up @orpc/client here ├─ app/ packages/ ├─ core-service/ // Define procedures with @orpc/server ├─ .../apps/ ├─ api/ // Import `core-service` and set up @orpc/server here ├─ web/ // Import `core-contract` and set up @orpc/client here ├─ app/ packages/ ├─ core-contract/ // Define contract with @orpc/contract ├─ core-service/ // Import `core-contract` and implement itMap OpenAPI responses using compact output mode
mainIn
compactmode (the default), the procedure's return value is used as the response body. The HTTP status code is determined bysuccessStatus, which defaults to200(must be in the2xxrange and less than400).const getPlanet = os .meta(openapi({ method: 'GET', path: '/planets', successStatus: 200 })) .handler(async () => { return { id: 'earth', name: 'Earth' } })Install @orpc/evlog and evlog
mainTo integrate Evlog with oRPC for structured logging and request tracing, install both
@orpc/evlog@betaandevlog@betausing your preferred package manager.npm install @orpc/evlog@beta evlog@beta # or yarn add @orpc/evlog@beta evlog@beta # or pnpm add @orpc/evlog@beta evlog@beta # or bun add @orpc/evlog@beta evlog@beta # or deno add npm:@orpc/evlog@beta npm:evlog@betaAccess request headers in oRPC procedures
mainOnce the
RequestHeadersHandlerPluginis installed, you can access headers viacontext.reqHeaders. To ensure type safety, extendRequestHeadersHandlerPluginContextin your server context definition.context.reqHeadersprovides access to the standard Web APIHeadersobject, allowing you to use methods like.get().import { os } from '@orpc/server' // ---cut--- import { getCookie } from '@orpc/server/helpers' import type { RequestHeadersHandlerPluginContext } from '@orpc/server/plugins' interface ServerContext extends RequestHeadersHandlerPluginContext {} const base = os.$context<ServerContext>() const example = base .use(({ context, next }) => { // Accessing headers via helpers like getCookie const sessionId = getCookie(context.reqHeaders, 'session_id') return next() }) .handler(({ context }) => { // Accessing headers directly via .get() const userAgent = context.reqHeaders?.get('user-agent') return { userAgent } })Consume AsyncIteratorObject in Client
mainAn
AsyncIteratorObjectallows you to consume streaming data like anAsyncGenerator. You first await the client call to obtain the iterator, then use afor await...ofloop to process events as they arrive.import { asyncIteratorObject, oc, RouterContractClient } from '@orpc/contract' import { z } from 'zod' const contract = { streaming: oc.output(asyncIteratorObject(z.object({ message: z.string() }))) } declare const client: RouterContractClient<typeof contract> // ---cut--- const iterator = await client.streaming() for await (const event of iterator) { console.log(event.message) }