Serverless
realtime backends, fully typed.

Use your schema library of choice, connect from typed clients, and keep realtime state in sync without socket boilerplate.

server.ts
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 });
client.ts
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.

server.ts Socket.io
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", [...]);
  });
});
chat.ts Zocket
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