npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jokio/rpc

v0.7.3

Published

Type-safe RPC framework with Zod validation for Express and TypeScript

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 zod

Usage

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 instance
  • routes: Route definitions object
  • handlers: Handler functions for each route with optional configuration
    • ctx: Optional function (req: Request) => TContext to provide context to handlers
    • validation: Optional boolean to enable response validation (default: false)
    • schemaFile: Optional path to expose route schemas at /__routes endpoint
    • GET, POST, PUT, PATCH, DELETE, QUERY: Handler functions that receive (data, ctx) parameters
      • data.params: Path parameters (e.g., :id in /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 options
    • baseUrl: Base URL for API requests
    • getHeaders: 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 requests
    • options.params: Path parameters
    • options.queryParams: Query parameters
  • POST(path, body, options?): For POST requests
  • PUT(path, body, options?): For PUT requests
  • PATCH(path, body, options?): For PATCH requests
  • DELETE(path, body, options?): For DELETE requests
  • QUERY(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