skills/rivet-dev/skills/rivetkit-client-javascript

rivetkit-client-javascript

Summary

JavaScript client for connecting to Rivet Actors with stateless or stateful connections.

  • Supports browser, Node.js, and Bun environments with automatic endpoint detection via environment variables or explicit configuration
  • Offers two interaction modes: stateless action calls for independent requests and stateful connections with real-time event subscriptions
  • Includes low-level HTTP and WebSocket access for actors implementing onRequest or onWebSocket handlers
  • Provides compound array-based keys for hierarchical actor addressing and built-in error handling via ActorError with code and metadata inspection
SKILL.md

RivetKit JavaScript Client

Use this skill when building JavaScript clients (browser, Node.js, or Bun) that connect to Rivet Actors with rivetkit/client.

First Steps

  1. Install the client (latest: 2.2.0)
    npm install rivetkit@2.2.0
    
  2. Create a client with createClient() and call actor actions.

Error Handling Policy

  • Prefer fail-fast behavior by default.
  • Avoid try/catch unless absolutely needed.
  • If a catch is used, handle the error explicitly, at minimum by logging it.

Getting Started

See the backend quickstart guide for getting started.

Minimal Client

import { createClient } from "rivetkit/client";
import type { registry } from "./index";

const client = createClient<typeof registry>({
  endpoint: "https://my-namespace:pk_...@api.rivet.dev",
});
const counter = client.counter.getOrCreate(["my-counter"]);
const count = await counter.increment(1);
import { actor, setup } from "rivetkit";

export const counter = actor({
  state: { count: 0 },
  actions: {
    increment: (c, x: number) => {
      c.state.count += x;
      return c.state.count;
    },
  },
});

export const registry = setup({
  use: { counter },
});

registry.start();

Stateless vs Stateful

import { createClient } from "rivetkit/client";

const client = createClient();
const handle = client.counter.getOrCreate(["my-counter"]);

// Stateless: each call is independent
await handle.increment(1);

// Stateful: keep a connection open for realtime events
const conn = handle.connect();
conn.on("count", (value) => console.log(value));
await conn.increment(1);

Getting Actors

import { createClient } from "rivetkit/client";

const client = createClient();
const room = client.chatRoom.getOrCreate(["room-42"]);
const existing = client.chatRoom.get(["room-42"]);

const created = await client.game.create(["game-1"], {
  input: { mode: "ranked" },
});

const byId = client.chatRoom.getForId("actor-id");
const resolvedId = await room.resolve();

Connection Parameters

import { createClient } from "rivetkit/client";

const client = createClient();
const chat = client.chatRoom.getOrCreate(["general"], {
  params: { authToken: "jwt-token-here" },
});

const conn = chat.connect();
import { createClient } from "rivetkit/client";

async function getAuthToken(): Promise<string> {
  return "jwt-token-here";
}

const client = createClient();
const chat = client.chatRoom.getOrCreate(["general"], {
  getParams: async () => ({
    authToken: await getAuthToken(),
  }),
});

const conn = chat.connect();

Use params for static connection parameters. Use getParams when the value can change between connection attempts, such as refreshing a JWT before each .connect() or reconnect.

Subscribing to Events

import { createClient } from "rivetkit/client";

const client = createClient();
const conn = client.chatRoom.getOrCreate(["general"]).connect();
conn.on("message", (msg) => console.log(msg));
conn.once("gameOver", () => console.log("done"));

Connection Lifecycle

import { createClient } from "rivetkit/client";

const client = createClient();
const conn = client.chatRoom.getOrCreate(["general"]).connect();

conn.onOpen(() => console.log("connected"));
conn.onClose(() => console.log("disconnected"));
conn.onError((err) => console.error("error:", err));
conn.onStatusChange((status) => console.log("status:", status));

await conn.dispose();

Low-Level HTTP & WebSocket

For actors that implement onRequest or onWebSocket, call them directly:

import { createClient } from "rivetkit/client";

const client = createClient();
const handle = client.chatRoom.getOrCreate(["general"]);

const response = await handle.fetch("history");
const history = await response.json();

const ws = await handle.webSocket("stream");
ws.addEventListener("message", (event) => {
  console.log("message:", event.data);
});
ws.send("hello");

Calling from Backend

import { Hono } from "hono";
import { createClient } from "rivetkit/client";

const app = new Hono();
const client = createClient();

app.post("/increment/:name", async (c) => {
  const counterHandle = client.counter.getOrCreate([c.req.param("name")]);
  const newCount = await counterHandle.increment(1);
  return c.json({ count: newCount });
});

Error Handling

import { ActorError } from "rivetkit/client";
import { createClient } from "rivetkit/client";

const client = createClient();

try {
  await client.user.getOrCreate(["user-123"]).updateUsername("ab");
} catch (error) {
  if (error instanceof ActorError) {
    console.log(error.code, error.metadata);
  }
}

Concepts

Keys

Keys uniquely identify actor instances. Use compound keys (arrays) for hierarchical addressing:

import { createClient } from "rivetkit/client";
import type { registry } from "./index";

const client = createClient<typeof registry>("http://localhost:6420");

// Compound key: [org, room]
client.chatRoom.getOrCreate(["org-acme", "general"]);
import { actor, setup } from "rivetkit";

export const chatRoom = actor({
  state: { messages: [] as string[] },
  actions: {
    getRoomInfo: (c) => ({ org: c.key[0], room: c.key[1] }),
  },
});

export const registry = setup({
  use: { chatRoom },
});

registry.start();

Don't build keys with string interpolation like "org:${userId}" when userId contains user data. Use arrays instead to prevent key injection attacks.

Environment Variables

createClient() automatically reads:

  • RIVET_ENDPOINT (endpoint)
  • RIVET_NAMESPACE
  • RIVET_TOKEN
  • RIVET_RUNNER

Defaults to http://localhost:6420 when unset. RivetKit runs on port 6420 by default.

Endpoint Format

Endpoints support URL auth syntax:

https://namespace:token@api.rivet.dev

You can also pass the endpoint without auth and provide RIVET_NAMESPACE and RIVET_TOKEN separately. For serverless deployments, use your app's /api/rivet URL. See Endpoints for details.

API Reference

Package: rivetkit

See the RivetKit client overview.

Need More Than the Client?

If you need more about Rivet Actors, registries, or server-side RivetKit, add the main skill:

npx skills add rivet-dev/skills

Then use the rivetkit skill for backend guidance.

Weekly Installs
2.4K
GitHub Stars
9
First Seen
Jan 26, 2026
Installed on
github-copilot2.4K
codex1.7K
opencode1.7K
gemini-cli1.7K
amp1.6K
kimi-cli1.6K