@jokio/rpc
v0.7.3
Published
Type-safe RPC framework with Zod validation for Express and TypeScript
Maintainers
Readme
@jokio/rpc
A type-safe RPC framework for TypeScript with Zod validation, designed for Express servers and HTTP clients.
Use Cases
Front-end → Backend | Backend → Backend
Features
- Full TypeScript type safety from server to client
- Runtime validation using Zod schemas
- Express.js integration for server-side
- Flexible fetch-based client with custom fetch support
- Support for multiple HTTP methods (GET, POST, PUT, PATCH, DELETE, QUERY)
- Path parameters, query parameters, and request body validation
- Automatic response validation
Installation
npm install @jokio/rpc zodUsage
1. Define Your Routes
import { defineRoutes } from "@jokio/rpc"
import { z } from "zod"
const routes = defineRoutes({
GET: {
"/user/:id": {
queryParams: z.object({
include: z.string().optional(),
}),
response: z.object({
id: z.string(),
name: z.string(),
email: z.string(),
}),
},
},
POST: {
"/user": {
body: z.object({
name: z.string(),
email: z.string().email(),
}),
response: z.object({
id: z.string(),
name: z.string(),
email: z.string(),
}),
},
},
PUT: {
"/user/:id": {
body: z.object({
name: z.string().optional(),
email: z.string().email().optional(),
}),
response: z.object({
id: z.string(),
name: z.string(),
email: z.string(),
}),
},
},
})2. Set Up the Server
import express from "express"
import { registerExpressRoutes } from "@jokio/rpc"
const app = express()
app.use(express.json())
const router = express.Router()
registerExpressRoutes(router, routes, {
GET: {
"/user/:id": async ({ params, queryParams }) => {
// params.id is type-safe and contains the :id from the path
// queryParams.include is validated by Zod
return {
id: params.id,
name: "John Doe",
email: "[email protected]",
}
},
},
POST: {
"/user": async ({ body }) => {
// body is validated by Zod
return {
id: "2",
name: body.name,
email: body.email,
}
},
},
PUT: {
"/user/:id": async ({ params, body }) => {
// Both params and body are type-safe
return {
id: params.id,
name: body.name ?? "John Doe",
email: body.email ?? "[email protected]",
}
},
},
})
app.use("/api", router)
app.listen(3000)3. Create a Type-Safe Client
import { createClient } from "@jokio/rpc"
const client = createClient(routes, {
baseUrl: "http://localhost:3000/api",
validate: true, // Optional: validate requests on client-side
})
// Fully typed API calls with path parameters
const user = await client.GET("/user/:id", {
params: { id: "23" },
queryParams: { include: "profile" },
})
// POST request with body
const newUser = await client.POST("/user", {
name: "Jane Doe",
email: "[email protected]",
})
// PUT request with path parameters and body
const updatedUser = await client.PUT(
"/user/:id",
{
name: "Jane Smith",
},
{
params: { id: "23" },
}
)API Reference
defineRoutes(routes)
Helper function to define routes with type inference.
Parameters:
routes: Route definitions object containing method configurations (GET, POST, PUT, PATCH, DELETE, QUERY)
Route Configuration:
body: Zod schema for request body (not available for GET)queryParams: Zod schema for query parameters (optional)response: Zod schema for response data
registerExpressRoutes(router, routes, handlers)
Registers route handlers to an Express router with automatic validation.
Parameters:
router: Express Router instanceroutes: Route definitions objecthandlers: Handler functions for each route with optional configurationctx: Optional function(req: Request) => TContextto provide context to handlersvalidation: Optional boolean to enable response validation (default: false)schemaFile: Optional path to expose route schemas at/__routesendpointGET,POST,PUT,PATCH,DELETE,QUERY: Handler functions that receive(data, ctx)parametersdata.params: Path parameters (e.g.,:idin/user/:id)data.body: Request body (validated by Zod)data.queryParams: Query parameters (validated by Zod)
createClient(routes, options)
Creates a type-safe HTTP client.
Parameters:
routes: Route definitions object (same as used on the server)options: Client configuration optionsbaseUrl: Base URL for API requestsgetHeaders: Optional function that returns headers (sync or async)fetch: Optional custom fetch function (useful for Node.js or testing)validate: Enable client-side request validation (default: true)debug: Enable debug logging (default: false)
Client Methods:
Each HTTP method has a type-safe method on the client:
GET(path, options?): For GET requestsoptions.params: Path parametersoptions.queryParams: Query parameters
POST(path, body, options?): For POST requestsPUT(path, body, options?): For PUT requestsPATCH(path, body, options?): For PATCH requestsDELETE(path, body, options?): For DELETE requestsQUERY(path, body, options?): For QUERY requests (custom method)
Type Safety
The library provides end-to-end type safety:
// TypeScript knows the exact shape of requests and responses
const result = await client.POST("/user", {
name: "John",
email: "invalid-email", // Zod will catch this at runtime
})
// result is typed as { id: string; name: string; email: string }
console.log(result.id)
// Path parameters are type-safe
const user = await client.GET("/user/:id", {
params: { id: "123" }, // TypeScript enforces correct parameter names
})
// Query parameters are validated
const users = await client.GET("/user/:id", {
params: { id: "123" },
queryParams: { include: "profile" }, // Must match Zod schema
})Error Handling
The library throws errors for:
- HTTP errors (non-2xx responses)
- Validation errors (invalid request/response data)
- Missing path parameters
try {
await client.POST("/user", invalidData)
} catch (error) {
// Handle validation or HTTP errors
}
// Missing path parameters will throw an error
try {
await client.GET("/user/:id", {
params: {}, // Missing 'id' parameter
})
} catch (error) {
// Error: Missing required parameter: "id" for path "/user/:id"
}License
MIT
