oRPC Documentation

repository·main·Indexed 26 days ago

https://github.com/middleapi/orpc

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

Tokens
116.6K
Snippets
359
Records
535
Agent score
90%

What's inside oRPC

  1. Overview of oRPC core packages

    main

    oRPC 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.
  2. Overview of oRPC features and capabilities

    main

    oRPC 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.
  3. Understand the oRPC Protocol and Serializer

    main
    The 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 including Date, BigInt, RegExp, URL, Set, Map, Blob, File, AsyncIteratorObject, and ReadableStream<Uint8Array>.
  4. Understand the oRPC Architecture

    main

    oRPC 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, and BigInt.
    • OpenAPI Support: Provides handlers, links, and generators to implement standards-compliant remote procedure invocation following the OpenAPI Specification.
  5. Setup OpenTelemetry instrumentation

    main

    Use ORPCInstrumentation to 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(),
      ],
    })
  6. Organize oRPC Monorepo Structures

    main

    oRPC supports three primary architectural patterns for monorepos. The recommended approach is to separate the server component (with composite enabled) into a dedicated package.

    Best Practices:

    • Use linked workspace packages (e.g., PNPM Workspace protocol) instead of alias imports inside server components.
    • In /apps, use references in tsconfig.json to depend on packages.
    • In /packages, enable composite: true in tsconfig.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 it
  7. Map OpenAPI responses using compact output mode

    main

    In compact mode (the default), the procedure's return value is used as the response body. The HTTP status code is determined by successStatus, which defaults to 200 (must be in the 2xx range and less than 400).

    const getPlanet = os
      .meta(openapi({ method: 'GET', path: '/planets', successStatus: 200 }))
      .handler(async () => {
        return { id: 'earth', name: 'Earth' }
      })
  8. Install @orpc/evlog and evlog

    main

    To integrate Evlog with oRPC for structured logging and request tracing, install both @orpc/evlog@beta and evlog@beta using 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@beta
  9. Access request headers in oRPC procedures

    main

    Once the RequestHeadersHandlerPlugin is installed, you can access headers via context.reqHeaders. To ensure type safety, extend RequestHeadersHandlerPluginContext in your server context definition.

    context.reqHeaders provides access to the standard Web API Headers object, 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 }
      })
  10. Consume AsyncIteratorObject in Client

    main

    An AsyncIteratorObject allows you to consume streaming data like an AsyncGenerator. You first await the client call to obtain the iterator, then use a for await...of loop 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)
    }