Raypx

API Overview

Type-safe API layer using oRPC with Zod validation.

Raypx uses oRPC as its type-safe API layer. oRPC provides end-to-end type safety from server procedures to client calls, with Zod validation ensuring data integrity at every boundary.

What is oRPC?

oRPC is a type-safe RPC framework for TypeScript. Unlike REST, it preserves full type information from your server procedures through to the client -- no code generation or OpenAPI parsing needed. Key features:

  • End-to-end type safety -- input/output types flow from server to client automatically.
  • Middleware composition -- chain auth, validation, and logging middleware.
  • Multiple protocols -- RPC for direct calls, OpenAPI for documentation.
  • Framework agnostic -- works with any HTTP framework.

Entry Point

All RPC requests are handled at a single catch-all route:

/api/rpc/$

The route in src/routes/api/rpc/$.ts uses a dual-handler pattern:

src/routes/api/rpc/$.ts
import { RPCHandler } from "@orpc/server/fetch"
import { OpenAPIHandler } from "@orpc/openapi/fetch"

const rpcHandler = new RPCHandler(appRouter, {
  interceptors: [onError((error) => {
    logger.error("rpc.error", { feature: "rpc", error: serializeError(error) })
  })],
})

const apiHandler = new OpenAPIHandler(appRouter, {
  plugins: [new OpenAPIReferencePlugin({
    schemaConverters: [new ZodToJsonSchemaConverter()],
  })],
})

Requests are tried against the RPC handler first. If no RPC match is found, the OpenAPI handler takes over (serving the reference UI).

Context Creation

Every RPC request receives a context object created from the incoming request:

packages/rpc/src/context.ts
import { auth } from "@raypx/auth"

export async function createContext({ req }: { req: Request }) {
  const session = await auth.api.getSession({ headers: req.headers })
  return { session }
}

export type Context = Awaited<ReturnType<typeof createContext>>

The context is typed and flows into every procedure handler. On the server, the router client calls procedures directly (bypassing HTTP). On the client, requests go through the RPC link.

Router Structure

The root router (appRouter) composes sub-routers:

packages/rpc/src/routers/index.ts
export const appRouter = {
  healthCheck: publicProcedure.handler(async () => { /* ... */ }),
  privateData: protectedProcedure.handler(({ context }) => { /* ... */ }),
  profile: profileRouter,
  documents: documentRouter,
  conversations: conversationRouter,
  usage: usageRouter,
}
RouterDescription
healthCheckPublic health check for database and storage.
privateDataProtected test endpoint.
profileUser profile get and update.
documentsDocument CRUD operations.
conversationsConversation and messaging.
usageUsage metrics and billing data.

Public vs Protected

  • publicProcedure -- No authentication required. Used for health checks and public endpoints.
  • protectedProcedure -- Requires a valid session. Throws UNAUTHORIZED if no session is found. Used for all user-facing features.

OpenAPI Reference

An auto-generated OpenAPI reference is available at:

/api/rpc/api-reference

See OpenAPI Reference for details.

Next Steps

On this page