kibinrpc

Getting Started

Install kibinrpc and set up your first typed RPC endpoint.

Installation

npm install @kibinrpc/server @kibinrpc/client

Server setup

1. Register actions

Use class decorators or plain functions — both produce the same router type:

// router.ts
import { ServerAction, defineActions, createRouter, KibinError } from '@kibinrpc/server'

class UserActions {
  @ServerAction()
  async getUser(id: string) {
    const user = await db.users.find(id)
    if (!user) throw new KibinError('NOT_FOUND', 'User not found')
    return user
  }

  @ServerAction()
  async listUsers() {
    return db.users.findAll()
  }
}

const postActions = defineActions({
  async listPosts() {
    return db.posts.findAll()
  },
})

export const router = createRouter({
  user: new UserActions(),
  post: postActions,
})

export type AppRouter = typeof router

Only functions decorated with @ServerAction() or wrapped with defineActions / serverAction are callable. Everything else is rejected.

2. Mount the handler

Mount on any framework that supports the Web Request/Response API:

// Hono
app.post('/api/rpc', (c) => router.handler(c.req.raw))

// Next.js App Router
export const POST = router.handler

// Vanilla node:http
import { createServer } from 'node:http'

createServer(async (req, res) => {
  if (req.method === 'POST' && req.url === '/api/rpc') {
    const chunks: Buffer[] = []
    for await (const chunk of req) chunks.push(chunk)
    const webReq = new Request('http://localhost/api/rpc', {
      method: 'POST',
      headers: req.headers as HeadersInit,
      body: Buffer.concat(chunks),
    })
    const webRes = await router.handler(webReq)
    res.writeHead(webRes.status, Object.fromEntries(webRes.headers))
    res.end(await webRes.text())
  }
})

The handler automatically detects single vs. batched requests — no second endpoint needed.

Client setup

// client.ts
import { createKibinClient } from '@kibinrpc/client'
import type { AppRouter } from './router'

export const client = createKibinClient<AppRouter>({
  baseUrl: '/api/rpc',
})

Use the client anywhere:

const user = await client.user.getUser('1')    // Promise<User>
const posts = await client.post.listPosts()    // Promise<Post[]>

Return types are inferred from the server — no manual annotations needed.

Next steps

On this page