Cap'n Web: a new RPC system for browsers and web servers

basanta sapkota

If you’ve ever tried to make “client calls server” feel like normal JavaScript, you already know how it usually ends. A thicket of fetch() wrappers. JSON glue. Three different callback shapes because each endpoint grew up in a different household. And at least one “why is this promise resolving to that?” moment.

Cap'n Web walks in with a pretty gutsy pitch: keep the ergonomics of calling local JS APIs, while still respecting the fact you’re on a network where latency exists, failures happen, and trust is… complicated. Fun.

Cap'n Web is open source, pure TypeScript, and it’s a JavaScript-native RPC protocol + implementation built for browsers and web servers. It stays intentionally small. under 10 kB minified+gzipped, zero dependencies. It also runs across modern JS runtimes like browsers, Node.js, Cloudflare Workers, and friends. Cloudflare calls it a “spiritual sibling” of Cap’n Proto, but shaped for the web stack. schema-less, JSON-based, transport-flexible.
Cloudflare announcement: https://blog.cloudflare.com/capnweb-javascript-rpc-library/

Key takeaways

  • Cap'n Web is JavaScript-native RPC for browsers and web servers, with very little boilerplate.
  • It uses an object-capability RPC model. Translation: you only get access to objects and functions you were explicitly handed. Security-by-construction vibes.
  • Bidirectional calling is supported. Server can call client, client can call server. No “requests only go one way” handcuffs.
  • You can pass functions and objects by reference using stubs, not just JSON-ish blobs.
  • Promise pipelining can collapse dependent calls into one network round trip a lot of the time.
  • Built-in transports include HTTP, WebSocket, and postMessage(), and you can extend it.

What is Cap'n Web, in one tight sentence

Cap'n Web is a JavaScript-native RPC system for browsers and web servers that uses object capabilities, supports passing functions/objects by reference, and enables promise pipelining over JSON-based messages.

Yeah, that’s dense. But the density is kind of the point, because each piece changes how you can design an API.

Why Cap'n Web for browsers and web servers, and why now

RPC has baggage. Part of the bad reputation comes from the old days when RPC often meant synchronous, blocking calls, the kind that made distributed systems feel like a house of cards.

Cloudflare’s take is basically this: a lot of the “RPC is evil” debate was forged back when async programming wasn’t what it is now. Now we’ve got Promise, async/await, cancellation patterns, streaming primitives, and runtimes that can keep sockets open without acting weird about it.

Cap'n Web leans hard into the modern world:

  • No schemas, unlike Cap’n Proto. It’s trying to feel natural in dynamic JS land.
  • Human-readable serialization. It’s JSON with pre-/post-processing, not a binary format.
  • It’s built around web transports and web constraints, not only datacenter microservices.

Primary reference remains Cloudflare’s post:
https://blog.cloudflare.com/capnweb-javascript-rpc-library/

The object-capability model, where security and expressiveness meet

Here’s the interesting bit: object capabilities or ocap.

In an ocap RPC system, a reference to a remote object is also a permission slip to use it. You don’t get to reach across the wire and poke at arbitrary stuff.Plus can only call objects and functions you’ve been handed as stubs during earlier calls.

Cloudflare explains the same concept in their Workers RPC docs. The system is designed so neither side can access arbitrary objects; you only invoke what you were explicitly given:
https://developers.cloudflare.com/workers/runtime-apis/rpc/visibility/

And once you internalize that, you realize Cap'n Web can do things most REST-ish APIs handle awkwardly:

  • Bidirectional calling, like “server calls client later”
  • Passing callbacks, meaning functions by reference
  • Passing stateful objects by reference without inventing IDs and /object/:id endpoints everywhere

Functions and objects by reference

Straight from the Cap'n Web repo behavior:

  • Pass a function and the receiver gets a stub. Calling the stub triggers an RPC back to where the function actually lives.
  • Pass an object by reference by having the class extend RpcTarget. Calls on the stub route back to the original object.

Repo: https://github.com/cloudflare/capnweb

Promise pipelining, the “wait… what?” feature

Promise pipelining is the one that makes people squint at the screen.

The vibe goes like this. You start an RPC call and get back a promise. Instead of awaiting it, you immediately use it in follow-up calls that depend on it. Cap'n Web records the chain, then ships it efficiently.

Cap’n Proto explains the idea as collapsing something like bar) into one round trip by sending “do foo” and “then do bar on foo’s result” together:
https://capnproto.org/rpc.html

Cap'n Web brings same idea into a JS-first, JSON-over-web environment. And it’s not just hand-waving. The Cap'n Web README shows an example where authenticate() → getUserId() → getUserProfile() can be built as a single batch using pipelined RpcPromise<T> values.

Transports you get out of the box

Cap'n Web supports a few transports without you having to play “pick a protocol and build a framework around it”:

  • HTTP, including batching patterns
  • WebSocket, for long-running interactive sessions
  • postMessage(), which is perfect for iframe and worker boundaries

That last one is sneakily huge if you’ve ever done serious Web Worker or cross-frame comms and wished it felt like normal method calls instead of message gymnastics.

Quick start code, real snippets

Minimal WebSocket client from Cloudflare’s post / README:

import { newWebSocketRpcSession } from "capnweb";

// One-line setup. Let api = newWebSocketRpcSession.

// Call a method on the server!
let result = await api.hello. Console.log;

Complete Cloudflare Worker RPC server:

import { RpcTarget, newWorkersRpcResponse } from "capnweb". Class MyApiServer extends RpcTarget {
  hello {
    return `Hello, ${name}!`
  }
}

export default {
  fetch(request, env, ctx) {
    let url = new URL(request.url). If (url.pathname === "/api") {
      return newWorkersRpcResponse(request, new MyApiServer()).
    }

    return new Response("Not found", { status: 404 });
  }
}

One small observation from building web apps: “one-line session setup” sounds like marketing fluff until you’ve lived through the alternative. It’s way easier to resist inventing a mini-framework when the happy path is genuinely short.

Internal link idea, related web app architecture work:
https://www.basantasapkota026.com.np/2026/03/adding-ai-features-to-my-tanstack-start.html

Best practices, the stuff keeps you out of trouble

  1. Design APIs like real JS objects. Small cohesive objects beat a giant “god service” most days. Let the server hand the client capabilities for narrow scopes.

  2. Use promise pipelining on purpose. If you’ve got dependent calls like auth → userId → profile, pipeline is your friend.Plus you actually need a value locally for rendering or branching, just await it like a normal person.

  3. Be clear on what’s passed by value vs by reference. Plain objects serialize like JSON trees. Custom classes should extend RpcTarget if you want reference semantics.

  4. Treat it like a security boundary. Ocap helps a lot, but you still validate inputs and handle abuse. No magic forcefield here.

Common mistakes people make

  • Assuming you can send any random object instance
    Cap'n Web does not support arbitrary class instances unless they extend RpcTarget. It also doesn’t support cyclic values. Messages are trees “like JSON” per the README.

  • Forgetting batching semantics
    In HTTP batch mode, the batch is sent when you await the relevant promises. If you never await something, Cap'n Web can avoid sending the return value back. That’s README behavior.

  • Overexposing surface area
    Ocap nudges you toward least-privilege. Don’t wrestle it to the ground. Hand out small capabilities and keep them tidy.

Where Cap'n Web fits

Cap'n Web is one of the more interesting RPC designs aimed squarely at browsers and web servers. The headline features aren’t just shiny bullet points. Object capabilities, bidirectional calls, passing functions/objects by reference, promise pipelining… those change what “API design” can look like on the web.

Not every app needs this much expressiveness. But if you’re building interactive tools, collaborative apps, or rich clients with server-driven workflows, this model is surprisingly clean.

If you try it, I’d genuinely be curious what transport you end up preferring: HTTP batch, WebSocket, or postMessage(). Also, which part feels natural right away and which part feels a little weird until it clicks.

Sources

  • Cloudflare Blog — Cap'n Web announcement. Https.//blog.cloudflare.com/capnweb-javascript-rpc-library/
  • GitHub — cloudflare/capnweb README and examples. Https.//github.com/cloudflare/capnweb
  • Cap’n Proto Docs — RPC Protocol and promise pipelining. Https.//capnproto.org/rpc.html
  • Cloudflare Workers Docs — RPC visibility and security model. Https.//developers.cloudflare.com/workers/runtime-apis/rpc/visibility/
  • Hacker News discussion. Https.//news.ycombinator.com/item?id=45332883
  • Reddit community discussion: https://www.reddit.com/r/programming/comments/1nnxrzd/capn_web_a_new_rpc_system_for_browsers_and_web/

Post a Comment