Cap'n Web is a spiritual sibling to Cap'n Proto (and is created by the same author), but designed to play nice in the web stack. That means:
- Like Cap'n Proto, it is an object-capability protocol. ("Cap'n" is short for "capabilities and.") It's incredibly powerful.
- Unlike Cap'n Proto, Cap'n Web has no schemas. In fact, it has almost no boilerplate whatsoever. This means it works more like the JavaScript-native RPC system in Cloudflare Workers.
- That said, it integrates nicely with TypeScript.
- Also unlike Cap'n Proto, Cap'n Web's underlying serialization is human-readable. It's just JSON, with a little pre- and post-processing.
- It works over HTTP, WebSocket, and
postMessage()out of the box, and can be extended to other transports easily. - It works in all major browsers, Cloudflare Workers, Node.js, Bun, Deno, and other modern JavaScript runtimes.
The whole thing compresses (minify + gzip) to under 16 kB with no dependencies.
Cap'n Web is more expressive than almost every other RPC system, because it implements an object-capability RPC model. That means it supports bidirectional calling, passing functions and objects by reference, promise pipelining (chaining dependent calls into a single network round trip), and capability-based security patterns, where holding a reference is the permission to use it.
npm i capnwebThere is no build step, no schema compiler, and no code generation.
import { RpcTarget, newWebSocketRpcSession } from "capnweb";To use using declarations, your tsconfig.json needs "target": "esnext" and matching libs.
See Installation.
A client looks like this:
import { newWebSocketRpcSession } from "capnweb";
// One-line setup.
let api = newWebSocketRpcSession("wss://example.com/api");
// Call a method on the server!
let result = await api.hello("World");
console.log(result);Here's the server:
import { RpcTarget, newWorkersRpcResponse } from "capnweb";
// This is the server implementation.
class MyApiServer extends RpcTarget {
hello(name) {
return `Hello, ${name}!`
}
}
// Standard Cloudflare Workers HTTP handler.
//
// (Node, Deno, Bun and other runtimes are supported too.)
export default {
fetch(request, env, ctx) {
// Parse URL for routing.
let url = new URL(request.url);
// Serve API at `/api`.
if (url.pathname === "/api") {
return newWorkersRpcResponse(request, new MyApiServer());
}
// You could serve other endpoints here...
return new Response("Not found", {status: 404});
}
}And here is the part that makes it interesting. Three dependent calls, one round trip:
using api = newHttpBatchRpcSession<Api>("https://example.com/api");
// No awaits, so no round trips yet.
using authed = api.authenticate(apiToken);
let friendIds = authed.getFriendIds();
// One await. One round trip. Everything above travelled together.
let friends = await friendIds.map(id => api.getUserProfile(id));The documentation site is the source of truth. It is an Astro + Starlight site
under packages/docs/, and every page is readable as Markdown directly on GitHub.
Start here:
| Page | What it covers |
|---|---|
| Introduction | What Cap'n Web is and why object capabilities matter |
| Quickstart | A working client and server |
| Pipelining tour | The part that makes it fast |
| How it compares | Against tRPC, JSON-RPC, GraphQL and Cap'n Proto |
Core concepts:
What can be passed ·
RpcTarget ·
RpcStub ·
RpcPromise & pipelining ·
The magic map() ·
Streaming ·
Disposal
Transports: Overview · HTTP batch · WebSocket · MessagePort · Custom
Server runtimes: Cloudflare Workers · Node.js · Deno · Bun · Hono · Other
Guides and reference: Security considerations · Sessions & reconnection · Runtime validation · Workers RPC interop · Wire protocol · API cheat sheet
To run the site locally, with both examples embedded as live in-browser playgrounds:
cd packages/docs && npm install && npm run devRunnable examples live in examples/:
batch-pipelining: three dependent calls in one HTTP round trip.worker-react: a React app against a Cap'n Web Worker, with runtime validation at the RPC boundary.session-recovery: a WebSocket session with a button that kills it, showing what a disconnect destroys and what it takes to resume without a gap.
capnweb-validate: generates runtime validators from your TypeScript types at build time, since TypeScript types are erased and a malicious peer can send anything.
Cap'n Web gives you strong authorization tools, but a few things are your responsibility: authenticating in-band rather than with cookies, rate-limiting because pipelining is cheap for attackers, setting transport payload limits, and validating types at runtime. Read Security considerations before exposing a service to untrusted peers.
To report a vulnerability, see SECURITY.md.
Bug reports and pull requests are welcome. Note that packages/docs/ is the source of truth for
user-facing documentation; behaviour changes should update the relevant page there.
