# birpc

> Message based Two-way remote procedure call

Latest version **4.2.0** (published 2026-08-19) · MIT license · 0 weekly downloads

## Install

```sh
npm install birpc
pnpm add birpc
yarn add birpc
bun add birpc
```

## Health

**Score 75/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; has provenance; recently updated; high maintenance score; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 4.2.0 |
| Published | 2026-08-19 |
| First published | 2021-12-26 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM |
| Dependencies | 0 |
| Unpacked size | 24.8 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 573 |
| Author | Anthony Fu <anthonyfu117@hotmail.com> |
| Maintainers | antfu, oreanno |
| Keywords | rpc, messages |

## Links

- npm: https://www.npmjs.com/package/birpc
- Repository: https://github.com/antfu-collective/birpc
- Homepage: https://github.com/antfu-collective/birpc#readme
- Issues: https://github.com/antfu-collective/birpc/issues
- Funding: https://github.com/sponsors/antfu
- npm.io page: https://npm.io/package/birpc

## Recent versions

- 4.2.0 (latest) — 2026-08-19
- 4.1.0 — 2026-08-10
- 4.0.0 — 2025-12-13
- 3.0.0 — 2025-12-03
- 2.9.0 — 2025-12-03
- 2.8.0 — 2025-11-09
- 2.7.0 — 2025-11-04
- 2.6.1 — 2025-09-23
- 2.6.0 — 2025-09-23
- 2.5.0 — 2025-07-14
- 2.4.0 — 2025-06-14
- 2.3.0 — 2025-03-25
- 2.2.0 — 2025-02-04
- 2.1.0 — 2025-02-04
- 2.0.19 — 2025-01-09
- … 25 more at https://npm.io/package/birpc/versions

## README

# birpc

[![NPM version](https://img.shields.io/npm/v/birpc?color=a1b858&label=)](https://www.npmjs.com/package/birpc)

Message-based two-way remote procedure call. Useful for WebSockets and Workers communication.

## Features

- Intuitive - call remote functions just like locals, with Promise to get the response
- TypeScript - safe function calls for arguments and returns
- Protocol agonostic - WebSocket, MessageChannel, any protocols with messages communication would work!
- Zero deps, ~0.5KB

## Examples

### Using WebSocket

When using WebSocket, you need to pass your custom serializer and deserializer.

#### Client

```ts
import type { ServerFunctions } from './types'

const ws = new WebSocket('ws://url')

const clientFunctions: ClientFunctions = {
  hey(name: string) {
    return `Hey ${name} from client`
  }
}

const rpc = createBirpc<ServerFunctions>(
  clientFunctions,
  {
    post: data => ws.send(data),
    on: fn => ws.on('message', fn),
    // these are required when using WebSocket
    serialize: v => JSON.stringify(v),
    deserialize: v => JSON.parse(v),
  },
)

await rpc.hi('Client') // Hi Client from server
```

#### Server

```ts
import type { ClientFunctions } from './types'
import { WebSocketServer } from 'ws'

const serverFunctions: ServerFunctions = {
  hi(name: string) {
    return `Hi ${name} from server`
  }
}

const wss = new WebSocketServer()

wss.on('connection', (ws) => {
  const rpc = createBirpc<ClientFunctions>(
    serverFunctions,
    {
      post: data => ws.send(data),
      on: fn => ws.on('message', fn),
      serialize: v => JSON.stringify(v),
      deserialize: v => JSON.parse(v),
    },
  )

  await rpc.hey('Server') // Hey Server from client
})
```

### Circular References

As `JSON.stringify` does not supporting circular references, we recommend using [`structured-clone-es`](https://github.com/antfu/structured-clone-es) as the serializer when you expect to have circular references.

```ts
import { parse, stringify } from 'structured-clone-es'

const rpc = createBirpc<ServerFunctions>(
  functions,
  {
    post: data => ws.send(data),
    on: fn => ws.on('message', fn),
    // use structured-clone-es as serializer
    serialize: v => stringify(v),
    deserialize: v => parse(v),
  },
)
```
### Using MessageChannel

[MessageChannel](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel) will automatically serialize the message and support circular references out-of-box.

```ts
export const channel = new MessageChannel()
```

#### Bob

``` ts
import type { AliceFunctions } from './types'
import { channel } from './channel'

const Bob: BobFunctions = {
  hey(name: string) {
    return `Hey ${name}, I am Bob`
  }
}

const rpc = createBirpc<AliceFunctions>(
  Bob,
  {
    post: data => channel.port1.postMessage(data),
    on: fn => channel.port1.on('message', fn),
  },
)

await rpc.hi('Bob') // Hi Bob, I am Alice
```

#### Alice

``` ts
import type { BobFunctions } from './types'
import { channel } from './channel'

const Alice: AliceFunctions = {
  hi(name: string) {
    return `Hi ${name}, I am Alice`
  }
}

const rpc = createBirpc<BobFunctions>(
  Alice,
  {
    post: data => channel.port2.postMessage(data),
    on: fn => channel.port2.on('message', fn),
  },
)

await rpc.hey('Alice') // Hey Alice, I am Bob
```

### One-to-multiple Communication

Refer to [./test/group.test.ts](./test/group.test.ts) as an example.

### Using SSE + HTTP POST

birpc can also run over **Server-Sent Events** (server → client) paired with
**HTTP POST** (client → server), so a browser and an HTTP server can call each
other with the exact same DX as the WebSocket example above. Because SSE is only
half of a duplex channel, the `post`/`on` pairing takes a bit more code than a
WebSocket - it is **not** part of the package, but a copy-paste recipe you own:
[`examples/sse/channel`](./examples/sse/channel) (`shared.ts` + `client.ts` +
`server.ts`, dependency-free). Copy that folder into your project and adjust it.

#### Client

```ts
import type { ServerFunctions } from './types'
import { createBirpc } from 'birpc'
// copied from examples/sse/channel
import { createSseClientChannel } from './channel/client'

const clientFunctions: ClientFunctions = {
  hey(name: string) {
    return `Hey ${name} from client`
  },
}

const channel = createSseClientChannel('http://localhost:3737')

const rpc = createBirpc<ServerFunctions>(clientFunctions, {
  post: channel.post,
  on: channel.on,
  serialize: v => JSON.stringify(v),
  deserialize: v => JSON.parse(v),
})

await rpc.hi('Client') // Hi Client from server
```

#### Server

```ts
import type { ClientFunctions } from './types'
import { createServer } from 'node:http'
import { createBirpc } from 'birpc'
// copied from examples/sse/channel
import { createSseSessionManager } from './channel/server'

const serverFunctions: ServerFunctions = {
  hi(name: string) {
    return `Hi ${name} from server`
  },
}

const sessions = createSseSessionManager()

createServer(async (req, res) => {
  if (req.method === 'GET' && req.url === '/sse') {
    const { channel } = sessions.open(req, res)
    const rpc = createBirpc<ClientFunctions>(serverFunctions, {
      post: channel.post,
      on: channel.on,
      serialize: v => JSON.stringify(v),
      deserialize: v => JSON.parse(v),
    })
    await rpc.hey('Server') // Hey Server from client
    return
  }
  if (req.method === 'POST' && req.url === '/rpc')
    await sessions.handlePost(req, res)
}).listen(3737)
```

See [`examples/sse`](./examples/sse) for the complete, runnable demo (Node client +
server and a browser page) plus a writeup of how the transport works.

## Sponsors

<p align="center">
  <a href="https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg">
    <img src='https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg' alt="Sponsors"/>
  </a>
</p>

## License

[MIT](./LICENSE) License © 2021 [Anthony Fu](https://github.com/antfu)

---
_Source: https://npm.io/package/birpc · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
