This example runs the same gRPC server source directly in Node.js and as a
WebAssembly component using jco-std's Node.js API support. The HTTP/2 listener,
request handling, and service implementation all live in
src/server.js. Both runtimes are tested with Buf's gRPC client.
@bufbuild/buf and @bufbuild/protoc-gen-es are development
dependencies. Buf lints the schema and generates JavaScript bindings locally;
no Buf account, remote code-generation plugin, or separate protoc installation
is required. The server uses @connectrpc/connect for the gRPC protocol and
@bufbuild/protobuf for serialization.
Warning
Jco's Node.js built-in compatibility for components is experimental and subject to change. APIs, behavior, and generated component interfaces may change incompatibly without a semver-major release.
proto/example/greeter/v1/greeter.proto
defines GreeterService.Greet, a unary RPC that returns a greeting or rejects an
empty name with INVALID_ARGUMENT. The service also sets a response header and
a trailer containing the UTF-8 byte count of the supplied name.
| API or tool | Purpose |
|---|---|
node:http2 |
Create the shared HTTP/2 server and send response trailers. |
node:buffer |
Count UTF-8 bytes and construct empty response bodies. |
node:stream/consumers |
Collect the SDK's response stream as a buffer. |
| Buf CLI | Lint the schema, run the local code generator, and call the server over gRPC. |
| Connect router | Handle gRPC envelopes, protobuf messages, status codes, and metadata. |
The server uses Connect's router with only the gRPC protocol enabled. A small
bridge connects its request/response interface to Node's HTTP/2 API. The
@connectrpc/connect-node adapter also loads compression and other Node APIs;
this example uses the portable router directly and leaves compression disabled.
Both launchers execute the same bridge and service code.
Jco compiles src/server.js with --with-nodejs-http2-via direct, replacing Node
built-in imports with jco-std implementations. Protobuf decoding, gRPC handling,
and the service itself execute inside WebAssembly. The generated component
imports jco:node/http2@0.1.0 and exports jco:node/http2-callbacks@0.1.0, plus
the application's start and stop functions.
run-transpiled.js supplies jco-std's Node HTTP/2 host and
preview2-shim's WASI capabilities, then calls start. The host provides network
I/O. Import mappings preserve WIT interface names. Node's
--experimental-wasm-jspi flag enables the asynchronous component callbacks.
serve-node.js imports the same source directly on Node.
Use Node.js 24 or newer and pnpm. This example uses workspace packages to exercise the HTTP/2 trailer support from this checkout. From the repository root:
pnpm install
pnpm --filter @bytecodealliance/jco run build
pnpm --filter @bytecodealliance/jco-std run build:ts
pnpm --filter @bytecodealliance/preview2-shim run buildThen run the complete example:
cd examples/components/node-grpc-server
pnpm run allThe all script lints and generates the protobuf bindings, builds
component.wasm, transpiles it into dist/transpiled, and runs the e2e tests.
The tests start both servers on automatically allocated localhost ports and
call them using buf curl --protocol grpc --http2-prior-knowledge.
They also inspect the HTTP/2 response to verify protobuf framing, headers,
grpc-status trailers, Unicode input, repeated calls, multi-frame bodies,
invalid arguments, and unknown routes. The same script runs in the examples CI matrix.
Generated bindings under src/gen are ignored and recreated by pnpm run generate.
After pnpm run all, start the component server:
pnpm run serveIn another terminal in this directory:
pnpm run demoExpected output:
{
"message": "Hello, World!"
}To compare with native Node, stop the component server and run
pnpm run serve:node, then run the same demo. Both servers use port 3000 by
default. Set PORT to select another server port and call it explicitly:
pnpm exec buf curl --schema proto --protocol grpc --http2-prior-knowledge \
--data '{"name":"World"}' \
http://127.0.0.1:3001/example.greeter.v1.GreeterService/GreetThis local example uses cleartext HTTP/2 (h2c) and unary, uncompressed RPCs.
The component boundary buffers complete requests and responses. TLS,
authentication, server reflection, and streaming RPCs are outside this example.
Buf uses the local schema instead of server reflection.