Cloudflare Workers
pluv.io supports building real-time APIs with Cloudflare Workers through their Durable Objects API. You can define your handler and your DurableObject manually if you need more control, but if you'd like to get started quickly, check out createPluvHandler.
Using with Cloudflare Workers (manual)
Let's step through how we'd put together a real-time API for Cloudflare Workers. The examples below assumes a basic understanding of Cloudflare Workers and Durable Objects.
Install dependencies
1# For the server2npm install @pluv/io @pluv/platform-cloudflare34# Server peer-dependencies5npm install zod
Create PluvIO instance
Define an io (websocket client) instance on the server codebase:
1// backend/io.ts23import { createIO } from "@pluv/io";4import { platformCloudflare } from "@pluv/platform-cloudflare";5import { eq } from "drizzle-orm";6import { drizzle } from "drizzle-orm/d1";7import { schema } from "./schema";89type Env = {10 DB: D1Database;11};1213export const io = createIO({14 platform: platformCloudflare<Env>(),15 // Example of using Cloudflare's D1 database with drizzle-orm16 getInitialStorage: async ({ env, room }) => {17 const db = drizzle(env.DB, { schema });1819 const existingRoom = await db20 .select({ encodedState: schema.rooms.encodedState })21 .from(schema.rooms)22 .where(eq(schema.rooms.name, room))23 .get();2425 return existingRoom?.encodedState ?? null;26 },27 onRoomDeleted: async ({ encodedState, env, room }) => {28 const db = drizzle(env.DB, { schema });2930 await db31 .insert(schema.rooms)32 .values({33 name: room,34 encodedState,35 })36 .onConflictDoUpdate({37 target: schema.rooms.name,38 set: { encodedState },39 })40 .run();41 },42});4344// Export the websocket client io type, instead of the client itself45export type AppPluvIO = typeof io;
Attach to a RoomDurableObject
Next, create a RoomDurableObject
and attach our new pluv.io instance to the room:
1// server/RoomDurableObject.ts23import { type InferIORoom } from "@pluv/io";4import { AppPluvIO, io } from "./io";56export class RoomDurableObject implements DurableObject {7 private _io: InferIORoom<AppPluvIO>;89 constructor(state: DurableObjectState) {10 this._io = io.getRoom(state.id.toString());11 }1213 async fetch(request: Request) {14 if (request.headers.get("Upgrade") !== "WebSocket") {15 return new Response("Expected WebSocket", { status: 400 });16 }1718 const { 0: client, 1: server } = new WebSocketPair();1920 await this._io.register(server);2122 return new Response(null, { status: 101, webSocket: client });23 }24}
Forward request to RoomDurableObject
Lastly, integrate your RoomDurableObject
with your Cloudflare Worker's default handler:
1// server/index.ts23const parseRoomId = (url: string): string => {4 /* get room from req.url */5};67const handler = {8 async fetch(req: Request, env: Env): Promise<Response> {9 const roomId = parseRoomId(req.url);10 // In wrangler.toml:11 // [durable_objects]12 // bindings = [{ name = "rooms", class_name = "RoomDurableObject" }]13 const durableObjectId = env.rooms.idFromString(roomId);1415 const room = env.rooms.get(durableObjectId);1617 return room.fetch(request);18 },19};2021export default handler;
createPluvHandler
If you don't need to modify your DurableObject or Cloudflare Worker handler too specifically, @pluv/platform-cloudflare also provides a function createPluvHandler
to create a DurableObject and handler for you automatically.
1import { createIO } from "@pluv/io";2import { createPluvHandler, platformCloudflare } from "@pluv/platform-cloudflare";34const io = createIO({5 platform: platformCloudflare(),6});78const Pluv = createPluvHandler({9 // Your PluvIO instance10 io,11 // Your durable object binding, defined in wrangler.toml12 binding: "rooms",13 // Optional: Specify the base path from which endpoints are defined14 endpoint: "/api/pluv", // default15 // If your PluvIO instance defines authorization, add your authorization16 // logic here. Return a user if authorized, return null or throw an error17 // if not authorized.18 authorize(request: Request, roomId: string): Promise<User> {19 return {20 id: "abc123",21 name: "leedavidcs"22 };23 },24 // Optional: If you want to modify your response before it is returned25 modify: (request, response) => {26 if (request.headers.get("Upgrade") === "websocket") return response;2728 // Add custom headers if you want29 response.headers.append("access-control-allow-origin", "*");3031 return response;32 },33});3435// Export your Cloudflare Worker DurableObject with your own custom name36// Then in wrangler.toml:37// [durable_objects]38// bindings = [{ name = "rooms", class_name = "RoomDurableObject" }]39export const RoomDurableObject = Pluv.DurableObject;4041// Export your Cloudflare Worker handler42export default Pluv.handler;4344// Alternatively, define your own custom handler45export default {46 async fetch(request: Request, env: Env): Promise<Response> {47 const response = await Pluv.fetch(request, env);4849 // matched with the Pluv handler, return response50 if (response) return response;5152 // didn't match with Pluv handler, add your own worker logic53 // ...5455 return new Response("Not found", { status: 404 });56 }57};