realtime

Define a live channel once and push updates to every open screen, over WebSockets, Socket.IO or server events.

Real-timeClient and ServerBuilt oncrosswsPartySocketSocket.IO

What it is

@kitr/realtime pushes events from the server to the browser over channels. defineChannel declares a channel's params and events with schemas. The server emits; useChannel subscribes; each event's effects update the query cache so components re-render.

Runs on both sides. For one-off calls that return a value, use actions or API routes.

Install

kitr add realtime

First time? Run npm i -g @kitr/kitr once. Inside a project, the global kitr runs your project's own version.

Adds @kitr/realtime with crossws and partysocket, config/realtime.ts, an example channel, the client in app/realtime.ts and a .ws() route. Adds server first if missing. Every change.

Quick start

A channel both ends import, an emit on the server, a subscription in a component. First the channel:

// app/channels/projects.ts
import { defineChannel } from '@kitr/realtime'
import { z } from 'zod'

export const projectSchema = z.object({ id: z.string(), name: z.string(), teamId: z.string() })
export type Project = z.infer<typeof projectSchema>

export const projectChannel = defineChannel({
  name: 'projects',
  params: z.object({ id: z.string() }),
  events: {
    updated: {
      schema: projectSchema,
      effects: (project, { id }) => ({ set: [{ key: ['projects', 'detail', id], data: project }] }),
    },
  },
})

Add projectChannel to the .ws('/ws', { channels: [...] }) route in server/routes.ts, then emit from the controller that renames a project:

// server/controllers/projects_controller.ts
app.container.use('realtime').use(projectChannel, { id: params.id }).emit('updated', project)

Subscribe in the component that shows the project:

// app/features/projects/project-title.tsx
import { useQuery } from '@kitr/query'
import { useChannel } from '@kitr/realtime/react'
import { type Project, projectChannel } from '#app/channels/projects'
import { realtime } from '#app/realtime'

export function ProjectTitle({ id }: { id: string }) {
  useChannel(realtime(), projectChannel, { id })
  const { data } = useQuery<Project>({ queryKey: ['projects', 'detail', id], enabled: false })
  return <h1>{data?.name ?? 'Loading'}</h1>
}

When Grace saves a new name, every subscribed client validates the event and writes it into the cache. Ada's heading re-renders. No refetch. Next in the guide: Push your first update.

Built on

Transport Built on Use it for
WebSockets (default) crossws on the h3 server Most apps, on Node, Bun, Deno and Workers.
Socket.IO Socket.IO Existing Socket.IO clients, rooms and its reconnection behavior.
Server-Sent Events The browser's EventSource Hosts that block WebSockets, or one-way updates.

kitr adds defineChannel, schema validation, authentication and authorization, cache effects, the hooks, reconnection and the route extension.

Problems it solves

Further reading