@xtr-dev/zod-rpc
Version:
Simple, type-safe RPC library with Zod validation and automatic TypeScript inference
225 lines (175 loc) ⢠6.87 kB
Markdown
# zod-rpc
<div align="center">
**Simple, type-safe RPC library with Zod validation**
[](https://www.npmjs.com/package/@xtr-dev/zod-rpc)
[](https://www.typescriptlang.org/)
[](https://opensource.org/licenses/MIT)
š **[View API Documentation](https://xtr-dev.github.io/zod-rpc/)**
</div>
## ⨠Features
- š **Single Source of Truth**: Zod schemas define validation, TypeScript types, and documentation all at once
- š **Type Safety**: Full TypeScript support with automatic type inference
- ā” **Runtime Validation**: Automatic input/output validation using Zod schemas
- š **Multiple Transports**: WebSocket, HTTP, and WebRTC support
- š” **Real-time**: Perfect for chat apps, collaborative tools, and live updates
## š Quick Start
### Installation
```bash
npm install -dev/zod-rpc zod
```
### Usage
Creating type-safe RPC services is incredibly simple:
#### 1. Define your service (shared between client and server)
```typescript
// shared/services.ts
import { z } from 'zod';
import { defineService } from '@xtr-dev/zod-rpc';
export const userService = defineService('user', {
get: {
input: z.object({
userId: z.string().describe('Unique identifier for the user to retrieve')
}),
output: z.object({
name: z.string().describe('Full name of the user'),
email: z.string().email().describe('Email address of the user'),
age: z.number().min(0).max(120).describe('Age of the user in years')
})
},
create: {
input: z.object({
name: z.string().min(1).describe('Full name of the new user'),
email: z.string().email().describe('Email address for the new user'),
age: z.number().min(0).max(120).describe('Age of the user in years')
}),
output: z.object({
id: z.string().describe('Unique identifier assigned to the new user'),
success: z.boolean().describe('Whether the user was created successfully')
})
}
});
```
#### 2. Create your server
```typescript
// server.ts
import { createRPCServer } from '@xtr-dev/zod-rpc';
import { userService } from './shared/services';
const server = createRPCServer('ws://localhost:8080')
.implement(userService, {
get: async ({ userId }) => ({
name: `User ${userId}`,
email: `user${userId}.com`,
age: Math.floor(Math.random() * 50) + 18
}),
create: async ({ name, email, age }) => ({
id: Math.random().toString(36),
success: true
})
});
await server.start();
```
#### 3. Create your client
```typescript
// client.ts
import { createRPCClient } from '@xtr-dev/zod-rpc';
import { userService } from './shared/services';
const client = await createRPCClient({
url: 'ws://localhost:8080',
services: { user: userService }
});
// Fully typed method calls with automatic target resolution
const user = await client.user.get({ userId: '123' });
const newUser = await client.user.create({
name: 'Alice',
email: 'alice@example.com',
age: 30
});
// TypeScript automatically infers:
// - user: { name: string; email: string; age: number }
// - newUser: { id: string; success: boolean }
// Plus you get automatic validation and documentation from the same schemas!
```
### The Power of Single Source of Truth
With zod-rpc, your Zod schemas serve multiple purposes simultaneously:
```typescript
const userService = defineService('user', {
get: {
input: z.object({
userId: z.string().describe('Unique identifier for the user to retrieve')
}),
output: z.object({
name: z.string().describe('Full name of the user'),
email: z.string().email().describe('Email address of the user'),
age: z.number().min(0).max(120).describe('Age of the user in years')
})
}
});
// This single schema definition provides:
// ā
Runtime validation (invalid data is caught automatically)
// ā
TypeScript types (full IntelliSense support)
// ā
API documentation (descriptions show up in generated docs)
// ā
Input constraints (email format, age ranges, etc.)
// ā
Error messages (descriptive validation failures)
```
**No more maintaining separate:**
- TypeScript interface files
- Validation schemas
- API documentation
- Error handling logic
### Advanced Usage Options
#### Fluent Builder Pattern
For more complex configurations, use the fluent builder pattern:
```typescript
// Client with builder pattern
const client = await connect('ws://localhost:8080')
.withId('my-client')
.withTimeout(10000)
.withServices({ user: userService, math: mathService })
.build();
// Server with builder pattern
const server = createServer('ws://localhost:8080')
.withId('my-server')
.withTimeout(15000)
.implement(userService, userImplementation)
.implement(mathService, mathImplementation)
.build();
await server.start();
```
#### Custom Target and Options
```typescript
// Override automatic target resolution for specific calls
// Useful in multi-server environments or microservices
const user = await client.user.get({ userId: '123' }, {
target: 'user-service-east', // Route to specific server instance
timeout: 5000 // Custom timeout for this call
});
// Set default target for all calls from this client
// Useful when connecting to a specific server cluster
client.setDefaultTarget('production-cluster');
```
#### Understanding Target Resolution
```typescript
// By default, zod-rpc uses the service ID as the target
defineService('user', { ... }) // Automatically targets 'user'
defineService('math', { ... }) // Automatically targets 'math'
// You can override this behavior:
const client = await createRPCClient({
url: 'ws://localhost:8080',
services: { user: userService },
defaultTarget: 'auto' // Use service ID (default)
// defaultTarget: 'server' // Send all requests to 'server'
});
```
## Key Benefits
- **Single source of truth** - Zod schemas provide validation, types, and documentation in one place
- **Automatic type inference** - No manual type definitions needed
- **Runtime safety** - Zod validation catches errors early
- **Self-documenting APIs** - Schema descriptions become your API docs automatically
- **Simple mental model** - RPC calls work like local function calls
- **Multiple transport support** - Same API for WebSocket, HTTP, WebRTC, and more
- **Minimal boilerplate** - Focus on business logic, not RPC infrastructure
## Examples
Check out the `/examples/` directory for complete working examples:
- **`/examples/websocket/`** - WebSocket client/server with real-time communication
- **`/examples/http/`** - HTTP/Express middleware integration with REST-like patterns
## š License
Public Domain