oRPC is currently pre-stable, please report any issues on our Discord or GitHub 🚧
background
oRPC

Typesafe API's Made Simple 🪄

End-to-End Typesafe API's made easy in a toolkit that helps developers build robust TypeScript API's with a focus on developer experience, reliability.

import type { ,  } from '@orpc/server'
import { ,  } from '@orpc/server'
import { ,  } from '@orpc/zod'
import {  } from 'zod'
 
export type  = { ?: { : string } } | undefined
 
// global pub, authed completely optional
export const  = .<>()
export const  = .((, , ) => {
  /** put auth logic here */
  return .({})
})
 
export const  = .({
  : 
    .(
      .({
        : .(),
      }),
    )
    .(async (, , ) => {
      return {
        : .,
        : 'example',
      }
    }),
 
  : .('/posts').({
    : 
      .({
        : '/{id}', // custom your OpenAPI
        : 'GET',
      })
      .(
        .({
          : .(),
        }),
      )
      .(
        .({
          : .(),
          : .(),
          : .(),
        }),
      )
      .(async (, , ) => {
        if (!?.) {
          throw new ({
            : 'UNAUTHORIZED',
          })
        }
 
        const  = await .({
          : {
            : ., // from now user not undefined-able
          },
        })
 
        const  = . // do something on success
 
        return 
      })
      .((, , ) => {
        return {
          : 'example',
          : 'example',
          : 'example',
        }
      }),
 
    : 
      .(
        .({
          : .(),
          : .(),
          : .().('image/*'),
        }),
      )
      .(async (, , ) => {
        const  = . // file upload out of the box
 
        return {
          : 'example',
          : .,
          : .,
        }
      }),
  }),
})
 
export type  = <typeof >
export type  = <typeof >
 
// Modern runtime that support fetch api like deno, bun, cloudflare workers, even node can used
import {  } from 'node:http'
import {  } from '@orpc/openapi/node'
import { ,  } from '@orpc/server/node'
 
const  = new (, {
  : [
    new (),
  ],
})
const  = new ()
const  = new ([, ])
 
const  = ((, ) => {
  if (.?.('/api')) {
    return .(, , {
      : '/api',
      : {},
    })
  }
 
  . = 404
  .('Not found')
})
 
.(3000, () => {
  // eslint-disable-next-line no-console
  .('Server is available at http://localhost:3000')
})
 
//
//
 
export default 
import { ,  } from '@orpc/client'
import {  } from '@orpc/client/fetch'
import type {  } from 'examples/server'
 
const  = new ({
  : 'http://localhost:3000/api',
  // fetch: optional override for the default fetch function
  // headers: provide additional headers
})
 
const  = <typeof  /* or contract router */>()
 
//  File upload out of the box
const  = await ..({
  : 'My Amazing Title',
  : 'This is a detailed description of my content',
  : (.('thumb') as HTMLInputElement).[0]!,
})
 
..
  • createPost
  • getPost
// typesafe and completion out of box try { const = await ..({ : 'example' }) } catch () { if ( instanceof ) { // handle error } }

Build Robust, Typesafe Functions

export const updateUser = os
	.use(authMiddleware) // require auth
	.use(cache('5m')) // cache the output
	.route({
		path: '/users/{id}' // dynamic params support
		method: 'PATCH' // custom OpenAPI method
	})
	.input(z.object({
		id: z.bigint(),
		user: z.object({
			name: z.string(),
			avatar: oz.file().type('image/*')
		})
	}))
	.use(canMiddleware, (i) => i.id) // permission check by id
	.output(z.literal("success")) // validate output
	.handler(async (input) => /* handle user update */)

Only the .handler method is required. All other chain methods are optional.

With Middleware and the Procedure Builder, you can create reusable logic that ensures type safety and adds power and flexibility to your functions.

Use as a Regular Function

const updatedUser = await updateUser({ 
	id: 1992n,
	user: {
		name: 'unnoq',
		avatar: await readFile('/image.jpg'),
	}
})

The Procedure Client feature lets your procedures behave like regular TypeScript functions.

Expose It In Your Frontend with a Fully Typed Client

const updatedUser = await orpc.updateUser({ 
	id: 1992n,
	user: {
		name: 'unnoq',
		avatar: document.getElementById('avatar').files[0],
	}
})

Our Vanilla Client is fully typed and doesn't rely on generated code—thanks to TypeScript!

Seamless Integration with TanStack Query

// Fetch data with oRPC
const { data, status } = useQuery(
  orpc.posts.getPost.queryOptions({ input: { id: 'example' } })
);
 
// Perform a mutation and invalidate related queries on success
const { mutate, isPending } = useMutation(
  orpc.updateUser.mutationOptions({
    onSuccess() {
      queryClient.invalidateQueries({
        queryKey: orpc.post.getPost.key({ input: { id: 'example' } }),
      });
    },
  })
);
 
// Execute mutation with structured input
mutate({
  id: 1992n,
  user: {
    name: 'unnoq',
    avatar: document.getElementById('avatar').files[0], // Handle file uploads
  },
});

We now support React Query Integration, Vue Query Integration, and Pinia Colada Integration.

Access via OpenAPI Standard

curl -X PATCH https://example.com/api/users/1992 \
  -H "Content-Type: multipart/form-data" \
  -F "user[name]=unnoq" \
  -F "user[avatar]=@/path/to/your/image.jpg"

Features like Smart Conversion and Bracket Notation automatically convert 1992 into a bigint and seamlessly parse objects like user.

Use as a Server Action

// call directly from client
const onSubmit = () => { updateUser({ /***/ }) }
// use with a hook
const { execute, isPending, isError, error, output, input, status } = useAction(updateUser)
// createSafeAction and useSafeAction if you want catch error on client
// use as a form action
const updateUserFA = createFormAction({ 
	procedure: updateUser,
	schemaCoercers: [new ZodCoercer()],
	onSuccess: () => redirect('/some-where')
})
 
<form action={updateUserFA}>
  <input type="number" name="id" value="1992" />
  <input type="string" name="user[name]" value="unnoq" />
  <input type="file" name="user[avatar]" accept="image/*" />
</form>

With Smart Conversion and Bracket Notation, inputs are automatically parsed into the correct types, ensuring smooth data handling. Learn more about Server Action.

Dependency Injection with Context

type ORPCContext = { db: DB, user?: { id: string } }
 
const pub = os.context<ORPCContext>()
 
const createPost = pub
	.use((input, context, meta) => {
		if(!context.user){
			throw new ORPCError({ code: 'UNAUTHORIZED' })
		}
		
		return meta.next({
			context: {
				user: context.user // modify user context
			}
		})
	})
	.handler((input, context, meta) => {
  // ^ context.user is now guaranteed to be defined
	})

When you use Initial Context, every call to your procedure will require a valid ORPCContext.

Contract-First Development

const userContract = oc
	.route({/*something*/})
	.input({/*something*/})
	.output({/*something*/})
 
// All nested procedures will be embedded in the parent contract
const contract = os
	.contract(userContract)

With oRPC's Contract First Development, you can easily separate the procedure's definition from its implementation.

Modern Adapters

oRPC works seamlessly in any environment that supports the Fetch API, including Node.js, Bun, Deno, Next.js, Nuxt.js, Cloudflare Workers, Supabase Functions, and more. We offer first-class serverless support with a dedicated router optimized for cold start performance. Learn more about oRPC's Modern Adapters.

Performance

We focus on both runtime performance and TypeScript checking performance to ensure a developer-first experience. Benchmarks are coming soon!

Reliability

We are committed to delivering robust software. Our aim is 100% test coverage to ensure oRPC's reliability at every level.