API Contract Definition
This guide shows how to define your xRPC API contract using TypeScript and Zod schemas. The API contract is a pure DSL - it defines schemas and endpoints only, with no implementation details. Handlers are implemented separately in server files.
Important: Your contract file must export router:
export const router = createRouter({ ... });
Core Concepts
An xRPC API contract uses a hierarchical structure:
- Router: Primary grouping mechanism that exports all endpoints (single export per contract)
- Endpoints: Named API namespaces (like
greeting,user,product) - Queries & Mutations: Individual RPC methods within an endpoint
- Types: Shared data schemas defined with Zod (like protobuf messages)
The hierarchy is: Router → Endpoints → Queries/Mutations
Simple Example
import { z } from 'zod';
import { createRouter, createEndpoint, query, mutation } from 'xrpckit';
// Types: Shared data schemas
const GreetingInput = z.object({ name: z.string() });
const GreetingOutput = z.object({ message: z.string() });
// Endpoint: Named API namespace
const greeting = createEndpoint({
// Query: Read operation
greet: query({
input: GreetingInput,
output: GreetingOutput,
}),
// Mutation: Write operation
setGreeting: mutation({
input: z.object({ name: z.string(), greeting: z.string() }),
output: GreetingOutput,
}),
});
// Router: Primary grouping mechanism (single export)
export const router = createRouter({
greeting, // Endpoint
});
Multiple Endpoints
import { z } from 'zod';
import { createRouter, createEndpoint, query, mutation } from 'xrpckit';
// User endpoint
const user = createEndpoint({
getUser: query({
input: z.object({ id: z.string() }),
output: z.object({ id: z.string(), name: z.string(), email: z.string() }),
}),
updateUser: mutation({
input: z.object({ id: z.string(), name: z.string().optional(), email: z.string().optional() }),
output: z.object({ id: z.string(), name: z.string(), email: z.string() }),
}),
});
// Product endpoint
const product = createEndpoint({
listProducts: query({
input: z.object({ limit: z.number().optional(), offset: z.number().optional() }),
output: z.array(z.object({ id: z.string(), name: z.string(), price: z.number() })),
}),
createProduct: mutation({
input: z.object({ name: z.string(), price: z.number() }),
output: z.object({ id: z.string(), name: z.string(), price: z.number() }),
}),
});
// Router: Primary grouping mechanism (single export)
export const router = createRouter({
user, // Endpoint
product, // Endpoint
});
Inline Types
For simple cases, define types inline:
import { z } from 'zod';
import { createRouter, createEndpoint, query, mutation } from 'xrpckit';
export const router = createRouter({
user: createEndpoint({
getUser: query({
input: z.object({ id: z.string() }),
output: z.object({ id: z.string(), name: z.string() }),
}),
updateUser: mutation({
input: z.object({ id: z.string(), name: z.string() }),
output: z.object({ id: z.string(), name: z.string() }),
}),
}),
});
Shared Types
For reusable types, define them separately:
import { z } from 'zod';
import { createRouter, createEndpoint, query, mutation } from 'xrpckit';
// Shared types
const UserId = z.string();
const User = z.object({ id: UserId, name: z.string(), email: z.string() });
const UserUpdate = z.object({ name: z.string().optional(), email: z.string().optional() });
export const router = createRouter({
user: createEndpoint({
getUser: query({
input: z.object({ id: UserId }),
output: User,
}),
updateUser: mutation({
input: z.object({ id: UserId }).merge(UserUpdate),
output: User,
}),
}),
});
Query vs Mutation
Each method within an endpoint is either a query or mutation:
- Query: Read operations that don’t modify state
- Mutation: Write operations that modify state
Both use the same structure with input and output properties. No handlers in the contract - those are implemented separately in server files (see Go Server, TypeScript Server (planned), or Kotlin Server (planned)).
Code Generation
After defining your contract, generate code for your target frameworks:
# Generate for specific framework targets
xrpc generate --targets go-server,ts-client
Planned targets: ts-express, go-client, and kotlin-springboot-server are not yet available in the CLI.
Note: The xRPC CLI runs on Node.js (>= 18). Generated code runs on native runtimes for each target language (Go runtime, Node.js/Bun, etc.).