Skip to content

oRPC: Put End-to-End Type Safety and OpenAPI on the Same Path

Aug 22, 2026 1 min
TL;DR oRPC supports implementation-first and contract-first APIs, offers an RPC client, and can expose the same router through OpenAPI 3.1.1 HTTP endpoints.
Table of Contents
  1. Start from implementation or contract
  2. Two handlers, one business implementation
  3. Lazy routers have contract costs
  4. Comparing tRPC and ts-rest
  5. When to choose oRPC
  6. References

🌏 中文版

oRPC addresses a common TypeScript API choice. Internal development wants function-like end-to-end types, while external consumers need conventional HTTP paths, OpenAPI documentation, and language-neutral clients. The same procedures can run through an RPCHandler or an OpenAPIHandler.

This is not a mechanical RPC-to-Swagger conversion. Route metadata, input and output schemas, errors, and serialization form the contract required for a useful OpenAPI surface.

Start from implementation or contract

Implementation-first code defines route, schema, and handler with a builder:

const findPlanet = os
  .route({ method: 'GET', path: '/planets/{id}' })
  .input(z.object({ id: z.number().int() }))
  .output(PlanetSchema)
  .handler(async ({ input }) => db.planet.find(input.id));

Contract-first development puts input, output, route, and error definitions in an independent package before the server implements them. The former suits rapid iteration by one team; the latter suits separate repositories or API review before business logic. Standard Schema support allows validators beyond Zod, including Valibot and ArkType.

Middleware, context, and typed errors remain server responsibilities. A schema rejects malformed payloads but cannot replace resource authorization. Every procedure must check actor, tenant, and resource together.

Two handlers, one business implementation

RPCHandler with RPCLink preserves native oRPC transport and types. OpenAPIHandler accepts conventional GET, POST, and other HTTP requests from route metadata. The same router can generate an OpenAPI 3.1.1 specification. Official runtimes include Node.js, Bun, Deno, and Cloudflare Workers.

const handler = new OpenAPIHandler(router, {
  plugins: [new CORSPlugin()],
});

export default async function fetch(request: Request) {
  const { matched, response } = await handler.handle(request, {
    prefix: '/api',
    context: {},
  });
  return matched ? response : new Response('Not Found', { status: 404 });
}

OpenAPI transport still obeys HTTP encoding constraints. File, Blob, Date, BigInt, streams, and nested multipart values require tests across server, generated client, and schema converter. A correct TypeScript hover is not sufficient evidence.

Lazy routers have contract costs

Routers are nested ordinary objects and can load lazily to improve cold starts. Importing a contract derived directly from an implementation router can pull internal logic into a client bundle. The official unlazy and minify flow emits only routing metadata.

Contract-first is cleaner for public APIs and multiple repositories because the shared package contains no implementation from the start. Existing OpenAPI can also generate an oRPC contract through a Hey API plugin, though that path is currently beta and should be version-pinned with artifact diffs.

Comparing tRPC and ts-rest

tRPC flows the server router type directly to the client and has the largest mature ecosystem; its official OpenAPI package is currently alpha. ts-rest centers on a conventional REST contract and is a smaller incremental layer over existing frameworks. oRPC brings RPC, OpenAPI, native types, streaming, and multiple runtimes into a more comprehensive framework.

That completeness creates more governance surfaces. Teams must decide whether RPC and OpenAPI clients are both supported, which artifact is canonical, how serializers are versioned, and whether renaming a procedure changes its path. A useful test is one route containing a typed error and Date, exercised through RPCLink, curl, and a generated OpenAPI client.

When to choose oRPC

oRPC is well positioned when one TypeScript backend serves an internal frontend, external integrations, and AI tools from one procedure definition. tRPC is more mature when a monorepo only needs the shortest internal path. ts-rest is easier to explain when the requirement is a thin REST contract layer. With oRPC, commit and diff the OpenAPI artifact in CI instead of trusting inference alone.

References