Skip to content

unnoq/orpc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

0cdb7b6 · Apr 22, 2025
Feb 28, 2025
Dec 18, 2024
Apr 22, 2025
Apr 22, 2025
Apr 22, 2025
Feb 25, 2025
Dec 30, 2024
Apr 18, 2025
Nov 19, 2024
Apr 15, 2025
Apr 20, 2025
Oct 3, 2024
Apr 22, 2025
Apr 18, 2025
Dec 27, 2024
Apr 20, 2025
Feb 27, 2025
Feb 27, 2025
Feb 27, 2025
Feb 13, 2025
Mar 30, 2025

Repository files navigation

oRPC logo

Typesafe APIs Made Simple 🪄

oRPC is a powerful combination of RPC and OpenAPI, makes it easy to build APIs that are end-to-end type-safe and adhere to OpenAPI standards


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.

Documentation

You can find the full documentation here.

Packages

Overview

This is a quick overview of how to use oRPC. For more details, please refer to the documentation.

  1. Define your router:

    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
      }
    }
  2. Create your server:

    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')
    )
  3. Create your client:

    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)
  4. Consume your API:

    import { orpc } from './client'
    
    const planets = await orpc.planet.list({ limit: 10 })
  5. Generate OpenAPI Spec:

    import { OpenAPIGenerator } from '@orpc/openapi'
    import { ZodToJsonSchemaConverter } from '@orpc/zod'
    
    const generator = new OpenAPIGenerator({
      schemaConverters: [new ZodToJsonSchemaConverter()]
    })
    
    const spec = await generator.generate(router, {
      info: {
        title: 'Planet API',
        version: '1.0.0'
      }
    })
    
    console.log(spec)

Sponsors

References

oRPC is inspired by existing solutions that prioritize type safety and developer experience. Special acknowledgments to:

  • tRPC: For pioneering the concept of end-to-end type-safe RPC and influencing the development of type-safe APIs.
  • ts-rest: For its emphasis on contract-first development and OpenAPI integration, which have greatly inspired oRPC’s feature set.

License

Distributed under the MIT License. See LICENSE for more information.