website/src/content/docs/actors/request-handler.mdx
For most use cases, actions provide high-level API powered by HTTP that's easier to work with than low-level HTTP. However, low-level handlers are required when implementing custom use cases or integrating external libraries that need direct access to the underlying HTTP Request/Response objects or WebSocket connections.
The onRequest handler processes HTTP requests sent to your actor. It receives the actor context and a standard Request object and returns a Response object.
import { actor } from "rivetkit";
export const counterActor = actor({
state: {
count: 0,
},
// WinterTC compliant - accepts standard Request and returns standard Response
onRequest: (c, request) => {
const url = new URL(request.url);
if (request.method === "GET" && url.pathname === "/count") {
return Response.json({ count: c.state.count });
}
if (request.method === "POST" && url.pathname === "/increment") {
c.state.count++;
return Response.json({ count: c.state.count });
}
return new Response("Not Found", { status: 404 });
},
actions: {}
});
import { actor, ActorContextOf } from "rivetkit";
import { Hono } from "hono";
// Define the actor first
const counterActor = actor({
state: { count: 0 },
actions: {}
});
// Build router with typed context
function buildRouter(actorCtx: ActorContextOf<typeof counterActor>) {
const app = new Hono();
app.get("/count", (honoCtx) => {
return honoCtx.json({ count: actorCtx.state.count });
});
app.post("/increment", (honoCtx) => {
actorCtx.state.count++;
return honoCtx.json({ count: actorCtx.state.count });
});
return app;
}
// Define the full actor with onRequest
export const counterActorWithRouter = actor({
state: { count: 0 },
vars: { app: null as Hono | null },
createVars: () => ({
app: null as Hono | null
}),
onRequest: async (c, request) => {
// Build router lazily
const app = buildRouter(c as ActorContextOf<typeof counterActor>);
return await app.fetch(request);
},
actions: {}
});
See also the raw fetch handler example.
Use the .fetch() method on an actor handle to send HTTP requests to the actor's onRequest handler. This can be executed from either your frontend or backend.
export const counter = actor({ state: { count: 0 }, onRequest: (c, request) => { if (request.method === "POST") c.state.count++; return Response.json(c.state); }, actions: {} });
export const registry = setup({ use: { counter } }); registry.start();
```typescript client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./index";
const client = createClient<typeof registry>("http://localhost:6420");
const actor = client.counter.getOrCreate(["my-counter"]);
// .fetch() is WinterTC compliant, it accepts standard Request and returns standard Response
const response = await actor.fetch("/");
const data = await response.json();
console.log(data); // { count: 0 }
Use .getGatewayUrl() to get the raw gateway URL for the actor. This is useful when you need to use the URL with external tools or custom HTTP clients.
export const counter = actor({ state: { count: 0 }, onRequest: (c, request) => { if (request.method === "POST") c.state.count++; return Response.json(c.state); }, actions: {} });
export const registry = setup({ use: { counter } }); registry.start();
```typescript client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./index";
const client = createClient<typeof registry>("http://localhost:6420");
const actor = client.counter.getOrCreate(["my-counter"]);
// Get the raw gateway URL
const gatewayUrl = await actor.getGatewayUrl();
// gatewayUrl = "https://...rivet.dev/..."
// Use with native fetch
const response = await fetch(`${gatewayUrl}/request/`);
const data = await response.json();
console.log(data); // { count: 0 }
This handler can be accessed with raw HTTP using https://api.rivet.dev/gateway/{actorId}/request/{...path}.
For example, to call POST /increment on the counter actor above:
// Replace with your actor ID and token
const actorId = "your-actor-id";
const token = "your-token";
const response = await fetch(
`https://api.rivet.dev/gateway/${actorId}/request/increment`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await response.json();
console.log(data); // { count: 1 }
curl -X POST "https://api.rivet.dev/gateway/{actorId}/request/increment" \
-H "Authorization: Bearer {token}"
The request is routed to the actor's onRequest handler where:
request.method is "POST"request.url ends with /increment (the path after /request/)See the HTTP API reference for more information on HTTP routing and authentication.
You can proxy HTTP requests from your own server to actor handlers using the RivetKit client. This is useful when you need to add custom authentication, rate limiting, or request transformation before forwarding to actors.
import { Hono } from "hono";
import { createClient } from "rivetkit/client";
import { serve } from "@hono/node-server";
const client = createClient();
const app = new Hono();
// Proxy requests to actor's onRequest handler
app.all("/actors/:id/:path{.*}", async (c) => {
const actorId = c.req.param("id");
const actorPath = (c.req.param("path") || "");
// Forward to actor's onRequest handler
const actor = client.counter.get(actorId);
return await actor.fetch(actorPath, c.req.raw);
});
serve(app);
onRequest will trigger the onBeforeConnect, onConnect, and onDisconnect hooks. Read more about lifecycle hooks.
Requests in flight will be listed in c.conns. Read more about connections.
The onRequest handler is WinterTC compliant and will work with existing libraries using the standard Request and Response types.
OPTIONS requests currently are handled by Rivet and are not passed to onRequestRequestContext - Context for HTTP request handlersActorDefinition - Interface for defining request handlers