In this article
April 28, 2025
April 28, 2025

oRPC: OpenAPI Remote Procedure Call for Type-Safe APIs

oRPC (OpenAPI Remote Procedure Call) combines the familiarity of RPC with the industry-standard OpenAPI spec so that every request/response is fully typed from client to server. 

Every seasoned engineer knows the real break-points in a system aren’t inside the algorithms—they’re in the seams where services talk.

Schema drift sneaks in during a late-night hot-fix, a version bump lands without regeneration, and suddenly the JSON your mobile team committed to memory is 2 fields off. You patch, you document, somebody forgets—repeat.

oRPC ends that loop by collapsing “definition,” “implementation,” and “documentation” into a single, type-safe contract that travels unchanged from server to edge to browser. Think RPC ergonomics, OpenAPI guarantees, and zero code-gen. If keeping micro-services honest feels like half your sprint, read on.

Why another API toolkit?

End-to-end type-safety

Types flow from server procedure → wire → client with no manual sync or code generation, eliminating an entire class of integration bugs.

Contract-first or code-first

Start with plain TypeScript, or author an OpenAPI contract and let oRPC enforce it—either way, your source of truth lives in one place.

Minimal boilerplate, maximal DX

Define procedures like ordinary functions; forget repetitive route maps and hand-rolled serializers.

Multi-runtime portability

Run the same router on Node, Bun, Deno, Cloudflare Workers, or the browser without touching a line of business logic.

MIT-licensed & community-driven

Development happens in the open at unnoq/orpc—no vendor lock-in, permissive license, and a fast-growing plugin ecosystem.

Getting started

Install the core packages

npm i @orpc/server @orpc/client

Define a tiny “hello” router

// hello.ts
import { os }       from '@orpc/server';
import { z }        from 'zod';

export const hello = os
  .input(z.string())                    // expect a single string
  .handler(({ input }) => `Hello, ${input}!`);

export const router = { hello };

Spin up a server (Node example)

// server.ts
import { createServer } from 'node:http';
import { RPCHandler }   from '@orpc/server/node';
import { router }       from './hello';

const handler = new RPCHandler(router);

createServer((req, res) => handler.handle(req, res))
  .listen(3000, () => console.log('oRPC listening on :3000'));

Call it from any TS/JS runtime

// client.ts
import { createORPCClient } from '@orpc/client';
import { RPCLink }          from '@orpc/client/fetch';
import type { RouterClient } from '@orpc/server';
import { router }            from './hello';   // shared types

const orpc: RouterClient<typeof router> = createORPCClient(
  new RPCLink({ url: 'http://localhost:3000' })
);

console.log(await orpc.hello('Alice'));  // → "Hello, Alice!"

How it works


Tool

Sweet spot

Notable features

Vault by HashiCorp

Self-hosted, on-prem or multi-cloud

Dynamic secrets, fine-grained ACLs, pluggable auth back-ends

AWS Secrets Manager

AWS-centric workloads

Native rotation for RDS/Redshift, CloudWatch auditing

Infisical

Developer-first SaaS

GitHub sync, per-environment scopes, CLI that feels like dotenv on steroids

WorkOS AuthKit

Identity & RBAC overlay for MCP + Cloudflare

Enterprise SSO, fine-grained roles/permissions, audit-ready consent flows; pairs with secrets stores to gate agent actions WorkOS + Cloudflare MCP_ Plug and Play Auth for Agentic AI Builders — WorkOS.pdf](file-service://file-PrZxvqVaHsL7PVq467C2ac)

Patterns you’ll actually ship

Error handling

import { ORPCError } from '@orpc/server';

export const divide = os
  .input(z.object({ a: z.number(), b: z.number().nonzero() }))
  .handler(({ input: { a, b } }) => a / b);

On the client: 

try {
  await orpc.divide({ a: 10, b: 0 });
} catch (e) {
  if (e instanceof ORPCError && e.code === 'BAD_REQUEST') {
    /* graceful UI message */
  }
}

Auth middleware

import { ORPCError } from '@orpc/server';

const withAuth = os.$context<{ user?: User }>().use(({ context, next }) => {
  const user = verifyJwt(context.headers?.authorization);
  if (!user) throw new ORPCError('UNAUTHORIZED');
  return next({ context: { user } });
});

Compose withAuth into any procedure to protect it.

When to reach for oRPC

Micro-frontends & BFFs

Multiple UIs can share a single contract, guaranteeing that every feature team ships against the same typed boundary.

Server Actions on the Edge

A single .actionable() call turns any procedure into a React/Next 15 server action, complete with streamed payloads and manifest hydration.

Edge-first workloads

Deploy unchanged handlers to Cloudflare Workers, Deno Deploy, or Bun servers—latency stays low, and the contract remains intact.

Teams that care about OpenAPI

oRPC emits a full spec on demand, so your Swagger UI and Postman collections are always in sync with running code.

Final thoughts

If you’re tired of duplicating types between front-end and back-end—or bolting OpenAPI on after the fact—oRPC lets you write a function and automatically get a fully typed, OpenAPI-compliant endpoint.

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.