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!" }]
});

Three primitives. That's the API.

Zocket exposes an actor model, not a socket library. You describe state, describe methods, and read state on the client. The runtime handles the rest.

01 — Define

Actors own state.

Describe state with Zod. Describe methods with typed inputs. No classes, no decorators, no codegen.

const Room = actor({
  state: z.object({
    messages: z.array(z.string()).default([]),
  }),
  methods: {
    send: {
      input: z.string(),
      handler: ({ state, input }) => {
        state.messages.push(input);
      },
    },
  },
});
02 — Call

Clients call methods.

Full types flow from server to client. No REST, no OpenAPI, no tRPC boilerplate. Just import the app type.

const room = client.room("general");

await room.send("hello");
// ^ typed. typos fail at compile time.
03 — Subscribe

State streams back.

You subscribe to state, not events. Immer patches flow over WebSocket. React hooks included.

room.state.subscribe((s) => {
  console.log(s.messages);
});

// or in React:
const s = useActorState(room);

Everything realtime apps need, in one runtime.

No separate database service, no queue, no socket server, no scheduler to manage.

State next to compute

State lives in memory where your code runs. Reads and writes take microseconds, not network round-trips.

Live state on the client

Clients always see the latest state. Skip the fetch-cache-revalidate dance entirely.

WebSockets out of the box

Push-based and bidirectional. The right transport for anything that actually changes.

Workflows, queues, scheduling

Long-running jobs, durable queues, and timers. One runtime instead of three services.

No lost requests

Calls survive disconnects. Clients reconnect and the answer is waiting for them.

Scales to zero, scales to anything

Idle actors cost nothing. Traffic spikes get absorbed. No capacity planning, no idle servers.

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, clientId }) {
    const i = state.online.indexOf(clientId);
    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