typed actors
for realtime apps

Define stateful actors with typed methods, events, and state — then call them from any client with full end-to-end type safety over WebSockets.

end-to-end typesafety

With Zocket, your actor state, methods, and events are fully typed from server to client using Zod schemas. Never worry about sending the wrong data again.

server.ts

import { z } from "zod";
import { actor, createApp } from "@zocket/core";
const ChatRoom = actor({
state: z.object({
messages: z.array(z.object({
from: z.string(),
text: z.string(),
})).default([]),
}),
methods: {
sendMessage: {
input: z.object({ from: z.string(), text: z.string() }),
handler: ({ state, input, emit }) => {
state.messages.push(input);
emit("newMessage", input);
},
},
},
events: {
newMessage: z.object({ from: z.string(), textt: z.string() }), // whoops, typo
},
});
export const app = createApp({ actors: { chat: ChatRoom } });
symbol field
messages: Message[]
symbol field
online: number

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.sendMessage({ from: "Alice", text: "Hi!" });
room.state.subscribe((s) => {
console.log(s.|) // autocomplete works like a charm
});

realtime on steroids

Batteries included. Type-safe by default.

Middleware

Add authentication, logging, and rate limiting with fully type-safe middleware. Context flows through to your method handlers with complete type inference.

Implementing authentication for realtime apps has never been easier.

middleware.ts

import { z } from "zod";
import { actor, createApp } from "@zocket/core";
import { validateToken } from "./auth";
const PrivateRoom = actor({
state: z.object({
messages: z.array(z.object({
user: z.string(),
text: z.string(),
})).default([]),
}),
// Middleware runs before every method call
middleware: async ({ headers, next }) => {
const token = headers.get("authorization");
const user = await validateToken(token);
if (!user) throw new Error("Unauthorized");
return next({ user });
},
methods: {
send: {
input: z.object({ text: z.string() }),
handler: ({ state, input, ctx }) => {
// ctx.user is fully typed from middleware
state.messages.push({
user: ctx.user.name,
text: input.text,
});
},
},
},
});

Error Handling

Throw typed errors from method handlers and catch them on the client with full type information. Errors include codes and messages for clean, predictable error handling.

Input Validation

Every method input is validated at runtime using Zod schemas. Invalid payloads are rejected before reaching your handler, keeping your actor state consistent and your application safe.

client.ts

const room = client.game("room-1");
try {
await room.join({ name: "Alice" });
} catch (err) {
if (err.code === "FORBIDDEN") {
console.log(err.message); // "Room is full!"
}
}

server.ts

const GameRoom = actor({
state: z.object({
players: z.array(z.string()).default([]),
}),
methods: {
join: {
input: z.object({ name: z.string() }),
handler: ({ state, input }) => {
if (state.players.length >= 4) {
throw new ZocketError({
code: "FORBIDDEN",
message: "Room is full!",
});
}
state.players.push(input.name);
},
},
},
});

State Subscriptions

Subscribe to actor state with fine-grained selectors. Components only re-render when the slice they care about changes. State is synchronized via efficient JSON patches over the wire.

Multiple clients can subscribe to the same actor instance, each receiving real-time updates as state changes.

state.ts

// Subscribe to full actor state
const room = client.chat("general");
room.state.subscribe((state) => {
console.log(state.messages);
});
// React: selector-based subscriptions
const messages = useActorState(room, (s) => s.messages);
const online = useActorState(room, (s) => s.online);
// Multiple actor instances — each with independent state
const lobby = client.game("lobby");
const room1 = client.game("room-1");
const room2 = client.game("room-2");
// Clean up when done
room.$dispose();

ready to build?

Get started with Zocket in minutes. Define your first actor, connect a client, and ship realtime features with full type safety.

1. Install

bun add @zocket/core @zocket/server @zocket/client zod

2. Define Actors

Create typed actors with state schemas, methods, events, and lifecycle hooks — all validated by Zod.

3. Ship It

Connect clients with full type inference. Use React hooks for selector-based state subscriptions.

frequently asked questions

Everything you need to know about Zocket.