kibinrpc

Interceptors

Hook into the request/response lifecycle on both the server and client.

Interceptors let you add cross-cutting behaviour — authentication, logging, response transformation — without touching individual action implementations.

Server interceptors

Server interceptors run for every call, including each item inside a batched request.

import { createRouter, KibinError } from '@kibinrpc/server'

const router = createRouter({ user, post }, {
  beforeAction({ namespace, method, args, request }) { ... },
  afterAction({ namespace, method, result }) { ... },
  onError({ namespace, method, error }) { ... },
})

beforeAction

Runs before the action is called. Use it to authenticate, authorize, or inject context.

beforeAction({ namespace, method, args, request }) {
  const token = request.headers.get('Authorization')
  if (!token) throw new KibinError('UNAUTHORIZED', 'Missing token')
},

Throwing a KibinError here rejects the call with a structured error. Any other thrown value becomes INTERNAL_ERROR.

afterAction

Runs after a successful action. Return a value to replace result, or return nothing to leave it unchanged.

afterAction({ namespace, method, result }) {
  console.log(`← ${namespace}.${method}`, result)
  return result
},

onError

Runs when an action throws. Use it for logging or reporting. The error still propagates to the client.

onError({ namespace, method, error }) {
  Sentry.captureException(error, { extra: { namespace, method } })
},

Client interceptors

Client interceptors wrap individual RPC calls before they hit the network.

const client = createKibinClient<AppRouter>({
  baseUrl: '/api/rpc',
  interceptors: {
    request(ctx) { ... },
    response(ctx) { ... },
    error(ctx) { ... },
  },
})

request

Runs before every call. Return the (optionally modified) context.

interceptors: {
  request(ctx) {
    return {
      ...ctx,
      args: [{ ...ctx.args[0], token: getAuthToken() }],
    }
  },
}

ctx contains { namespace, method, args }. You can modify args but not namespace or method.

response

Runs after every successful call. Return the value the caller receives. Defaults to returning ctx.data unchanged.

interceptors: {
  response({ namespace, method, data }) {
    console.log(`← ${namespace}.${method}`, data)
    return data
  },
}

error

Runs on every failed call, after all retries are exhausted. Return a fallback value or rethrow.

interceptors: {
  error({ namespace, method, error }) {
    if (error.code === 'UNAUTHORIZED') {
      window.location.href = '/login'
    }
    throw error
  },
}

Combined example

A common pattern: add an auth token on the client, verify it on the server.

Client

const client = createKibinClient<AppRouter>({
  baseUrl: '/api/rpc',
  interceptors: {
    request: (ctx) => ({
      ...ctx,
      args: [{ ...ctx.args[0], token: localStorage.getItem('token') }],
    }),
    error: ({ error }) => {
      if (error.code === 'UNAUTHORIZED') window.location.href = '/login'
      throw error
    },
  },
})

Server

const router = createRouter({ user, post }, {
  beforeAction({ args, request }) {
    const token = request.headers.get('Authorization') ?? args[0]?.token
    if (!verifyToken(token)) throw new KibinError('UNAUTHORIZED', 'Invalid token')
  },
})

On this page