Back to Rivet

Cloudflare Workers Quickstart

website/src/content/docs/actors/quickstart/cloudflare-workers.mdx

2.2.19.3 KB
Original Source

import { Hosting } from "@/components/docs/Hosting";

<Steps> <Step title="Add Rivet Skill to Coding Agent (Optional)">

If you're using an AI coding assistant (like Claude Code, Cursor, Windsurf, etc.), add Rivet skills for enhanced development assistance:

sh
npx skills add rivet-dev/skills
</Step> <Step title="Install Rivet">
sh
npm install rivetkit @rivetkit/cloudflare-workers
</Step> <Step title="Create an Actor">

Create a simple counter actor:

ts
import { actor, setup } from "rivetkit";

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

export const registry = setup({
	use: { counter },
});
</Step> <Step title="Setup Server">

Choose your preferred web framework:

<Tabs> <Tab title="Default"> <CodeGroup workspace> ```ts actors.ts @hide import { actor, setup } from "rivetkit";

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

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


```ts index.ts
import { createHandler } from "@rivetkit/cloudflare-workers";
import { registry } from "./actors";

// The `/api/rivet` endpoint is automatically exposed here for external clients
const { handler, ActorHandler } = createHandler(registry);
export { handler as default, ActorHandler };
</CodeGroup> </Tab> <Tab title="Hono"> <CodeGroup workspace> ```ts actors.ts @hide import { actor, setup } from "rivetkit";

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

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


```ts index.ts
import { createHandler, type Client } from "@rivetkit/cloudflare-workers";
import { Hono } from "hono";
import { registry } from "./actors";

const app = new Hono<{ Bindings: { RIVET: Client<typeof registry> } }>();

app.post("/increment/:name", async (c) => {
	const client = c.env.RIVET;
	const name = c.req.param("name");

	// Get or create actor and call action
	const counter = client.counter.getOrCreate([name]);
	const newCount = await counter.increment(1);

	return c.json({ count: newCount });
});

// The `/api/rivet` endpoint is automatically exposed here for external clients
const { handler, ActorHandler } = createHandler(registry, { fetch: app.fetch });
export { handler as default, ActorHandler };
</CodeGroup> </Tab> <Tab title="Manual Routing"> <CodeGroup workspace> ```ts actors.ts @hide import { actor, setup } from "rivetkit";

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

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


```ts index.ts @nocheck
import { createHandler } from "@rivetkit/cloudflare-workers";
import { registry } from "./actors";

// The `/api/rivet` endpoint is automatically mounted on this router for external clients
const { handler, ActorHandler } = createHandler(registry, {
	fetch: async (request, env, ctx) => {
		const url = new URL(request.url);

		if (url.pathname.startsWith("/increment/")) {
			const name = url.pathname.split("/")[2];
			const client = env.RIVET;

			const counter = client.counter.getOrCreate([name]);
			const newCount = await counter.increment(1);

			return new Response(JSON.stringify({ count: newCount }), {
				headers: { "Content-Type": "application/json" },
			});
		}

		return new Response("Not Found", { status: 404 });
	}
});

export { handler as default, ActorHandler };
</CodeGroup> </Tab> <Tab title="Advanced"> <CodeGroup workspace> ```ts actors.ts @hide import { actor, setup } from "rivetkit";

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

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


```ts index.ts @nocheck
import { createInlineClient } from "@rivetkit/cloudflare-workers";
import { registry } from "./actors";

const {
	client,
	fetch: rivetFetch,
	ActorHandler,
} = createInlineClient(registry);

// IMPORTANT: Your Durable Object must be exported here
export { ActorHandler };

export default {
	fetch: async (request, env, ctx) => {
		const url = new URL(request.url);

		// Custom request handler
		if (request.method === "POST" && url.pathname.startsWith("/increment/")) {
			const name = url.pathname.slice("/increment/".length);

			const counter = client.counter.getOrCreate([name]);
			const newCount = await counter.increment(1);

			return new Response(JSON.stringify({ count: newCount }), {
				headers: { "Content-Type": "application/json" },
			});
		}

		// Optional: Mount /api/rivet path to access actors from external clients
		if (url.pathname.startsWith("/api/rivet")) {
			const strippedPath = url.pathname.substring("/api/rivet".length);
			url.pathname = strippedPath;
			const modifiedRequest = new Request(url.toString(), request);
			return rivetFetch(modifiedRequest, env, ctx);
		}

		return new Response("Not Found", { status: 404 });
	},
} satisfies ExportedHandler;
</CodeGroup> </Tab> </Tabs> </Step> <Step title="Run Server">

Configure your wrangler.json for Cloudflare Workers:

json
{
  "name": "my-rivetkit-app",
  "main": "src/index.ts",
  "compatibility_date": "2025-01-20",
  "compatibility_flags": ["nodejs_compat"],
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["ActorHandler"]
    }
  ],
  "durable_objects": {
    "bindings": [
      {
        "name": "ACTOR_DO",
        "class_name": "ActorHandler"
      }
    ]
  },
  "kv_namespaces": [
    {
      "binding": "ACTOR_KV",
      "id": "your_namespace_id"
    }
  ]
}

Start the development server:

sh
wrangler dev

Your server is now running at http://localhost:8787

</Step> <Step title="Test Your Actor">

Test your counter actor using HTTP requests:

<CodeGroup>
ts
// Increment counter
const response = await fetch("http://localhost:8787/increment/my-counter", {
	method: "POST"
});

const result = await response.json();
console.log("Count:", result.count); // 1
sh
# Increment counter
curl -X POST http://localhost:8787/increment/my-counter
</CodeGroup> </Step> <Step title="Deploy to Cloudflare Workers">

Deploy to Cloudflare's global edge network:

bash
wrangler deploy

Your actors will now run on Cloudflare's edge with persistent state backed by Durable Objects.

See the Cloudflare Workers deployment guide for detailed deployment instructions and configuration options.

</Step> </Steps>

Configuration Options

Connect To The Rivet Actor

Create a type-safe client to connect from your frontend or another service:

<Tabs> <Tab title="JavaScript"> <CodeGroup workspace> ```ts actors.ts @hide import { actor, setup } from "rivetkit";

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

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


```ts client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./actors";

// Create typed client (use your deployed URL)
const client = createClient<typeof registry>("https://your-app.workers.dev/api/rivet");

// Use the counter actor directly
const counter = client.counter.getOrCreate(["my-counter"]);

// Call actions
const count = await counter.increment(3);
console.log("New count:", count);

// Listen to real-time events
const connection = counter.connect();
connection.on("newCount", (newCount: number) => {
	console.log("Count changed:", newCount);
});

// Increment through connection
await connection.increment(1);
</CodeGroup>

See the JavaScript client documentation for more information.

</Tab> <Tab title="React"> <CodeGroup workspace> ```ts actors.ts @hide import { actor, setup } from "rivetkit";

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

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


```tsx Counter.tsx
import { createRivetKit } from "@rivetkit/react";
import { useState } from "react";
import type { registry } from "./actors";

const { useActor } = createRivetKit<typeof registry>("https://your-app.workers.dev/api/rivet");

function Counter() {
	const [count, setCount] = useState(0);

	const counter = useActor({
		name: "counter",
		key: ["my-counter"]
	});

	counter.useEvent("newCount", (x: number) => setCount(x));

	const increment = async () => {
		await counter.connection?.increment(1);
	};

	return (
		<div>
			<p>Count: {count}</p>
			<button onClick={increment}>Increment</button>
		</div>
	);
}
</CodeGroup>

See the React documentation for more information.

</Tab> </Tabs> <Note> Cloudflare Workers mounts the Rivet endpoint on `/api/rivet` by default. </Note>