JavaScript / TypeScript quick start¶
Expose echo and a reverse callback at ws://127.0.0.1:8000/ws/.
The complete examples use Node.js 24+ to run .ts directly; the compiled
library supports Node.js 22+.
Install¶
Choose a framework. The install and server tabs stay in sync. Before the first npm release, use the local package archives.
npm install @wsrpc/server @wsrpc/client
npm install @wsrpc/server @wsrpc/client express
npm install @wsrpc/server @wsrpc/client fastify
Start the server¶
Save the selected complete example as app.ts. Each handles ordinary HTTP
GET /health, registers its RPC methods and closes connections on shutdown.
import { createServer } from "node:http";
import { pathToFileURL } from "node:url";
import { WebSocketAsync } from "@wsrpc/server";
class RPC extends WebSocketAsync {}
RPC.addRoute("echo", (_socket, params: Record<string, unknown>) => params);
RPC.addRoute("callback", (socket, { value }: { value: number }) =>
socket.call<number>("double", { value }),
);
export function createApp() {
const server = createServer((request, response) => {
response.writeHead(request.url === "/health" ? 200 : 404, {
"content-type": "application/json",
});
response.end(
JSON.stringify(
request.url === "/health" ? { ok: true } : { error: "Not found" },
),
);
});
const rpc = RPC.attach(server, { path: "/ws/" });
return { server, rpc };
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
const { server, rpc } = createApp();
server.listen(Number(process.env.PORT ?? 8000), "127.0.0.1");
process.once("SIGINT", () => {
void rpc.close().then(() => server.close());
});
}
node app.ts
import { createServer } from "node:http";
import { pathToFileURL } from "node:url";
import express from "express";
import { WebSocketAsync } from "@wsrpc/server";
class RPC extends WebSocketAsync {}
RPC.addRoute("echo", (_socket, params: Record<string, unknown>) => params);
RPC.addRoute("callback", (socket, { value }: { value: number }) =>
socket.call<number>("double", { value }),
);
export function createApp() {
const app = express();
app.get("/health", (_request, response) => {
response.json({ ok: true });
});
const server = createServer(app);
const rpc = RPC.attach(server, { path: "/ws/" });
return { app, server, rpc };
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
const { server, rpc } = createApp();
server.listen(Number(process.env.PORT ?? 8000), "127.0.0.1");
process.once("SIGINT", () => {
void rpc.close().then(() => server.close());
});
}
node app.ts
import { pathToFileURL } from "node:url";
import Fastify from "fastify";
import { WebSocketAsync } from "@wsrpc/server";
class RPC extends WebSocketAsync {}
RPC.addRoute("echo", (_socket, params: Record<string, unknown>) => params);
RPC.addRoute("callback", (socket, { value }: { value: number }) =>
socket.call<number>("double", { value }),
);
export function createApp() {
const app = Fastify();
app.get("/health", async () => ({ ok: true }));
const rpc = RPC.attach(app.server, { path: "/ws/" });
app.addHook("preClose", async () => {
await rpc.close();
});
return { app, rpc };
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
const { app } = createApp();
await app.listen({
port: Number(process.env.PORT ?? 8000),
host: "127.0.0.1",
});
process.once("SIGINT", () => {
void app.close();
});
}
node app.ts
Connect a Node client¶
Save as client.ts and run node client.ts in another terminal:
import { WSRPCClient } from "@wsrpc/client/node";
const client = await WSRPCClient.connect(
process.argv[2] ?? "ws://127.0.0.1:8000/ws/",
{
routes: new Map([
["double", (_socket, { value }: { value: number }) => value * 2],
]),
},
);
try {
console.log(await client.call("echo", { message: "Hello" }));
console.log(await client.call("callback", { value: 21 }));
} finally {
await client.close();
}
It prints { message: 'Hello' } and 42. The callback route is registered
before connecting, so the server can call the client immediately. The
finally block closes the connection.
For a frontend, use the browser client. Continue with typed contracts, route classes and implicit context. The Python clients can call this server too.