---
title: "Typesafe APIs Made Simple with oRPC"
description: "Learn about ORPC a TypeScript library for API routing, validation, documentation, and type safety."
canonicalUrl: "https://zuplo.com/blog/2025/04/22/typesafe-apis-made-simple-with-orpc"
pageType: "blog"
date: "2025-04-22"
authors: "unnoq"
tags: "TypeScript, API Tooling, Tutorial"
image: "https://cdn.zuplo.com/cdn-cgi/image/fit=crop,width=1200,height=630/www/media/posts/2025-04-22-typesafe-apis-made-simple-with-orpc/image.png"
---
> This article is written by [unnoq](https://github.com/unnoq), creator of
> [ORPC](https://github.com/unnoq/orpc). All opinions expressed are their own.

Building APIs often involves navigating a complex landscape of tools for
routing, validation, documentation, and type safety. What if you could simplify
this while enhancing the developer experience?

Enter [oRPC](https://orpc.unnoq.com/), a TypeScript library designed to make
building end-to-end typesafe APIs effortless, automatically adhering to OpenAPI
standards and boosting developer productivity. It aims to provide the best
developer experience ever for the world of APIs.

<CalloutAudience
  variant="useIf"
  items={[
    `Building typesafe APIs with
TypeScript`,
    `Want end-to-end type safety from client to server`,
    `Need
automatic OpenAPI specification generation`,
    `Looking for a tRPC alternative
with native OpenAPI support`,
  ]}
/>

> oRPC just announced its v1 release! 🎉 For the full story and details behind
> the v1 launch, check out the
> [official announcement here](https://orpc.unnoq.com/blog/v1-announcement).

## The Idea behind oRPC

oRPC's philosophy is **powerful simplicity**. The goal is to let you define API
endpoints almost as easily as writing standard functions, while automatically
gaining significant benefits:

- End-to-end type safety (including inputs, outputs, and **errors**)
- Server Action compatibility
- Full OpenAPI specification generation
- Optional Contract-first workflow support
- Standard function call compatibility

Here’s a taste of the fluent API design:

```ts
const getting = os
  .use(dbProvider)
  .use(requiredAuth)
  .use(rateLimit)
  .use(analytics)
  .use(sentryMonitoring)
  .use(retry({ times: 3 }))
  .route({ method: "GET", path: "/getting/{id}" })
  .input(z.object({ id: z.string() }))
  .use(canGetting, (input) => input.id)
  .errors({
    SOME_TYPE_SAFE_ERROR: {
      data: z.object({ something: z.string() }),
    },
  })
  .handler(async ({ input, errors, context }) => {
    // do something
  })
  .actionable() // server action compatible
  .callable(); // regular function compatible
```

## oRPC highlights

- **🔗 End-to-End Type Safety**: Ensure type-safe inputs, outputs, and errors
  from client to server.
- **📘 First-Class OpenAPI**: Built-in support that fully adheres to the OpenAPI
  standard.
- **📝 Contract-First Development**: Optionally define your API contract before
  implementation.
- **⚙️ Framework Integrations**: Seamlessly integrate with TanStack Query
  (React, Vue, Solid, Svelte), Pinia Colada, and more.
- **🚀 Server Actions**: Fully compatible with React Server Actions on Next.js,
  TanStack Start, and other platforms.
- **🔠 Standard Schema Support**: Works out of the box with Zod, Valibot,
  ArkType, and other schema validators.
- **🗃️ Native Types**: Supports native types like Date, File, Blob, BigInt, URL,
  and more.
- **⏱️ Lazy Router**: Enhance cold start times with our lazy routing feature.
- **📡 SSE & Streaming**: Enjoy full type-safe support for SSE and streaming.
- **🌍 Multi-Runtime Support**: Fast and lightweight on Cloudflare, Deno, Bun,
  Node.js, and beyond.
- **🔌 Extendability**: Easily extend functionality with plugins, middleware,
  and interceptors.
- **🛡️ Reliability**: Well-tested, TypeScript-based, production-ready, and MIT
  licensed.

## Why oRPC? (Performance & DX)

Compared to popular alternatives like tRPC, oRPC was designed with first-class
OpenAPI support from the ground up, addressing a common need, especially for
developers working with tools like API Gateways (like Zuplo!).

Simple benchmark comparisons with tRPC also show promising results for oRPC (see
full report [here](https://github.com/unnoq/orpc-benchmarks)):

- Faster type checking ( **~1.6x** )
- Faster runtime request handling ( **~2.8x** )
- Lower max CPU usage ( **~1.26x less** )
- Lower max RAM usage ( **~2.6x less** )
- Smaller bundle size ( **~2x smaller** )

> **Warning** Benchmark results can vary significantly based on environment,
> project complexity, and specific usage patterns. Treat these numbers as a
> reference point, not an absolute guarantee.

oRPC aims to provide a highly performant and developer-friendly alternative for
building robust, typesafe APIs.

## Quick Start

Getting started with oRPC is straightforward.

### 1. Install

```bash
npm install @orpc/server@latest @orpc/client@latest zod # Use zod or your preferred schema validator
```

### 2. Define your first router

Create procedures (functions) and group them in a router object.

```ts
import type { IncomingHttpHeaders } from "node:http";
import { ORPCError, os } from "@orpc/server";
import { z } from "zod";

const PlanetSchema = z.object({
  id: z.number().int().min(1),
  name: z.string(),
  description: z.string().optional(),
});

export const listPlanet = os
  .input(
    z.object({
      limit: z.number().int().min(1).max(100).optional(),
      cursor: z.number().int().min(0).default(0),
    }),
  )
  .handler(async ({ input }) => {
    // your list code here
    return [{ id: 1, name: "name" }];
  });

export const findPlanet = os
  .input(PlanetSchema.pick({ id: true }))
  .handler(async ({ input }) => {
    // your find code here
    return { id: 1, name: "name" };
  });

export const createPlanet = os
  .$context<{ headers: IncomingHttpHeaders }>()
  .use(({ context, next }) => {
    const user = parseJWT(context.headers.authorization?.split(" ")[1]);

    if (user) {
      return next({ context: { user } });
    }

    throw new ORPCError("UNAUTHORIZED");
  })
  .input(PlanetSchema.omit({ id: true }))
  .handler(async ({ input, context }) => {
    // your create code here
    return { id: 1, name: "name" };
  });

export const router = {
  planet: {
    list: listPlanet,
    find: findPlanet,
    create: createPlanet,
  },
};
```

### 3. Create your server

oRPC offers adapters for various runtimes (Node.js, Cloudflare Workers, Deno,
Bun, etc.). Here’s a Node.js example:

```ts
import { createServer } from "node:http";
import { RPCHandler } from "@orpc/server/node";
import { CORSPlugin } from "@orpc/server/plugins";

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

const server = createServer(async (req, res) => {
  const result = await handler.handle(req, res, {
    context: { headers: req.headers },
  });

  if (!result.matched) {
    res.statusCode = 404;
    res.end("No procedure matched");
  }
});

server.listen(3000, "127.0.0.1", () =>
  console.log("Listening on 127.0.0.1:3000"),
);
```

### 4. Create TypeScript Client

Set up a client to consume your API type-safely.

```ts
import type { RouterClient } from "@orpc/server";
import { createORPCClient } from "@orpc/client";
import { RPCLink } from "@orpc/client/fetch";

const link = new RPCLink({
  url: "http://127.0.0.1:3000",
  headers: { Authorization: "Bearer token" },
});

export const orpc: RouterClient<typeof router> = createORPCClient(link);
```

### 5. Consume your API

Now you can call your API procedures with full autocompletion and type safety.

```ts
import { isDefinedError, safe } from "@orpc/client";

const planet = await orpc.planet.find({ id: 1 });
//    ^^^^^ Everything is type-safe

const { data, error } = await safe(orpc.planet.create({ name: "name" }));

if (error) {
  if (isDefinedError(error)) {
    // Handle known and type-safe errors here
  }

  // Handle unknown errors here
} else {
  // Handle success here
}
```

## Tanstack Query Integration

oRPC provides lightweight helpers for seamless integration with TanStack Query
(React, Vue, Solid, Svelte). Refer to the
[official documentation](https://orpc.unnoq.com/docs/tanstack-query/basic) for
detailed setup guides.

```ts
import { isDefinedError } from "@orpc/client";
import {
  useInfiniteQuery,
  useMutation,
  useQueryClient,
} from "@tanstack/react-query";

const query = useInfiniteQuery(
  orpc.planet.list.infiniteOptions({
    input: (cursor) => ({ cursor }),
    getNextPageParam: (lastPage) => lastPage.nextCursor,
    initialPageParam: 0,
  }),
);

const queryClient = useQueryClient();

const mutation = useMutation(
  orpc.planet.update.mutationOptions({
    onError(error) {
      if (isDefinedError(error)) {
        const id = error.data.id;
        //    ^    type-safe errors
      }
    },
    onSuccess() {
      queryClient.invalidateQueries({
        queryKey: orpc.planet.key(),
      });
    },
  }),
);
```

## Explore Further

We have many examples and playgrounds available, covering integrations from
vanilla JS to Next.js, SvelteKit, Vue, and more:

- **Playgrounds:**
  [https://orpc.unnoq.com/docs/playgrounds](https://orpc.unnoq.com/docs/playgrounds)
- **Full Documentation:** [https://orpc.unnoq.com/](https://orpc.unnoq.com/)

## Special Thanks to Zuplo

Developing open-source tools takes significant effort. We'd like to extend a
special thank you to **[Zuplo](https://zuplo.link/orpc) - the Serverless API
Gateway, designed for developers**. Their sponsorship and support during the
early stages of oRPC were invaluable and greatly appreciated. Thank you, Zuplo
team, for believing in the project!

<CalloutDoc
  title="Try Zuplo"
  description={`Zuplo is a serverless API gateway designed for developers. Add authentication, rate limiting, and documentation to any API.`}
  href="https://zuplo.com/docs/articles/step-1-setup-basic-gateway"
  features={[`Free tier available`, `OpenAPI-native`, `Deploy in seconds`]}
/>