Serverless
realtime backends,
fully typed.
Use your schema library of choice, connect from typed clients, and keep realtime state in sync without socket boilerplate.
import { actor, createApp, serve } from "@zocket/core";
import { z } from "zod";
const ChatRoom = actor({
state: z.object({
messages: z.array(z.object({
from: z.string(), text: z.string(),
})).default([]),
}),
methods: {
send: {
input: z.object({ from: z.string(), text: z.string() }),
handler: ({ state, input }) => { state.messages.push(input) },
},
},
});
export const app = createApp({ actors: { chat: ChatRoom } });
serve(app, { port: 3000 }); import { createClient } from "@zocket/client";
import type { app } from "./server";
const client = createClient<typeof app>({
url: "ws://localhost:3000",
});
const room = client.chat("general");
await room.send({ from: "alice", text: "hello!" });
room.state.subscribe((s) => {
console.log(s.messages);
// → [{ from: "alice", text: "hello!" }]
}); Same feature, very different runtime model.
Compare the amount of ceremony, infrastructure assumptions, and state management you carry into each use case.
Use case
Chat Room
A chat room with message history and online presence.
const io = new Server(3000);
const rooms = new Map();
io.on("connection", (socket) => {
let room = null, name = null;
socket.on("join", ({ room: r, name: n }) => {
room = r; name = n;
socket.join(r);
const state = getRoom(r);
state.online.add(n);
socket.emit("history", state.messages);
io.to(r).emit("presence", [...state.online]);
});
socket.on("sendMessage", ({ text }) => {
if (!room || !name) return;
const msg = { from: name, text };
getRoom(room).messages.push(msg);
io.to(room).emit("newMessage", msg);
});
socket.on("disconnect", () => {
if (!room || !name) return;
getRoom(room).online.delete(name);
io.to(room).emit("presence", [...]);
});
});const ChatRoom = actor({
state: z.object({
messages: z.array(z.object({
from: z.string(),
text: z.string(),
})).default([]),
online: z.array(z.string()).default([]),
}),
methods: {
join: {
input: z.object({ name: z.string() }),
handler: ({ state, input }) => {
state.online.push(input.name);
},
},
sendMessage: {
input: z.object({ from: z.string(), text: z.string() }),
handler: ({ state, input }) => {
state.messages.push(input);
},
},
},
onDisconnect({ state, connectionId }) {
const i = state.online.indexOf(connectionId);
if (i !== -1) state.online.splice(i, 1);
},
});
Takeaway
No manual broadcasting, no room management, no untyped events. State syncs automatically.
How it runs
Start with one process. Scale to a distributed cluster when you need it.
ready
01
Standalone
One process, zero infra. For prototypes and small apps.
❯ serve(app, { port: 3000 })
ready
02
Distributed
Gateway + NATS + runtime. Self-hosted, scales independently.
❯ docker compose up
❯ zocket deploy
coming soon
03
Platform
Multi-tenant, Firecracker isolation, fully hosted.
❯ zocket deploy --platform
Coming soon
These features are designed and in progress. Read the roadmap →
$ bun add @zocket/core @zocket/server @zocket/client zod
Read the Docs