Quickstart

Learn how to quickly get started and setup with @pluv/io.

Installation

pluv.io is broken up into multiple packages, so that you can install only what you need, particular to your codebase and framework selections.

PurposeLocationInstall command
Register websockets and custom eventsServernpm install @pluv/io yjs
Call and listen to events. Interact with shared storageClientnpm install @pluv/client yjs
React-bindings for @pluv/clientClientnpm install @pluv/react yjs
Adapter for Node.js runtimeServernpm install @pluv/platform-node ws
Adapter for Cloudflare Workers runtimeServernpm install @pluv/platform-cloudflare

Installation Example

Here is an example installation for npm, assuming you are building for Node.js, React and TypeScript.

1# For the server
2npm install @pluv/io @pluv/platform-node
3# Server peer-dependencies
4npm install yjs ws zod
5
6# For the client
7npm install @pluv/react
8# Client peer-dependencies
9npm install react react-dom yjs zod

Defining a backend PluvIO instance

Let's step through how we'd put together a real-time API for Node.js. In this example, this API will define 2 type-safe events.

Create PluvIO instance

Define an io (websocket client) instance on the server codebase:

1// backend/io.ts
2
3import { createIO } from "@pluv/io";
4import { platformNode } from "@pluv/platform-node";
5
6export const io = createIO({ platform: platformNode() });
7
8// Export the websocket client io type, instead of the client itself
9export type AppPluvIO = typeof io;

Create type-safe server events

Use io.event to define type-safe websocket events on the io instance. The two properties on the event function are:

  • input: zod validation schema that validates and casts the input for the event.
  • resolver: This is the implementation of the event. It accepts an input of the validated input of the incoming event, and returns an event record to emit back to the frontend client.
1// backend/io.ts
2
3import { createIO } from "@pluv/io";
4import { platformNode } from "@pluv/platform-node";
5import { z } from "zod";
6
7export const io = createIO({ platform: platformNode() })
8 // When event "SEND_MESSAGE" is sent by the frontend and received
9 // on the server
10 .event("SEND_MESSAGE", {
11 // Define a zod validation schema for the input
12 input: z.object({
13 message: z.string(),
14 }),
15 // Emit a "MESSAGE_RECEIVED" from the server to the client
16 resolver: ({ message }) => ({ MESSAGE_RECEIVED: { message } }),
17 })
18 .event("EMIT_EMOJI", {
19 input: z.object({
20 emojiCode: z.number(),
21 }),
22 resolver: ({ emojiCode }) => ({ EMOJI_RECEIVED: { emojiCode } }),
23 });
24
25// Export the io type instance of the io itself
26export type AppPluvIO = typeof io;

Integrate PluvIO with ws

Important: Demonstration is for Node.js only.

Integrate with ws on Node.js.

1// backend/server.ts
2
3import express from "express";
4import Http from "http";
5import WebSocket from "ws";
6import { io } from "./io";
7
8const PORT = 3000;
9
10const app = express();
11const server = Http.createServer();
12const wsServer = new WebSocket.Server({ server });
13
14const parseRoomId = (url: string): string => {
15 /* get room from req.url */
16};
17
18wsServer.on("connection", async (ws, req) => {
19 const roomId = parseRoomId(req.url);
20 const room = io.getRoom(roomId);
21
22 await room.register(ws);
23});
24
25server.listen(PORT, () => {
26 console.log(`Server is listening on port: ${port}`);
27});

Connecting the frontend to PluvIO

Now that the io instance is setup on the backend, we can setup the frontend client and connect the exported io type from the server.

Create the React bundle

1// frontend/io.ts
2
3import { createBundle, createClient, y } from "@pluv/react";
4import type { AppPluvIO } from "server/io";
5
6const client = createClient<AppPluvIO>({
7 // Define a ws endpoint url based on how you parse the room name
8 wsEndpoint: (room) => `ws://localhost:3000/api/room/${room}`,
9});
10
11export const {
12 // factories
13 createRoomBundle,
14
15 // components
16 PluvProvider,
17
18 // hooks
19 usePluvClient,
20} = createBundle(client);
21
22export const {
23 // components
24 MockedRoomProvider,
25 PluvRoomProvider,
26
27 // hooks
28 usePluvBroadcast,
29 usePluvConnection,
30 usePluvEvent,
31 usePluvMyPresence,
32 usePluvMyself,
33 usePluvOther,
34 usePluvOthers,
35 usePluvRoom,
36 usePluvStorage,
37} = createRoomBundle();

Wrap with your pluv.io providers

Wrap your component with PluvRoomProvider to connect to a realtime room and enable the rest of your room react hooks.

1// frontend/Room.tsx
2
3import { FC } from "react";
4import { PluvRoomProvider } from "./io";
5import { ChatRoom } from "./ChatRoom";
6
7export const Room: FC = () => {
8 return (
9 <PluvRoomProvider room="my-example-room">
10 <ChatRoom />
11 </PluvRoomProvider>
12 );
13};

Send and receive events

Use usePluvBroadcast and usePluvEvent to send and receive type-safe events in your React component.

1// frontend/ChatRoom.tsx
2
3import { FC, useCallback, useState } from "react";
4import { emojiMap } from "./emojiMap";
5import { usePluvBroadcast, usePluvEvent } from "./io";
6
7export const ChatRoom: FC = () => {
8 const broadcast = usePluvBroadcast();
9
10 const [messages. setMessages] = useState<string[]>([]);
11
12 usePluvEvent("MESSAGE_RECEIVED", ({ data }) => {
13 // ^? (property) data: { message: string }
14 setMessages((prev) => [...prev, data.message]);
15 });
16
17 usePluvEvent("EMOJI_RECEIVED", ({ data }) => {
18 // ^? (property) data: { emojiCode: number }
19 const emoji = emojiMap[data.emojiCode];
20
21 console.log(emoji);
22 });
23
24 const onMessage = useCallback((message: string): void => {
25 // 2nd parameter will be statically typed from server/io.ts
26 broadcast("SEND_MESSAGE", { message });
27 }, [broadcast]);
28
29 const onEmoji = useCallback((emojiCode: number): void => {
30 broadcast("EMIT_EMOJI", { emojiCode });
31 }, [broadcast]);
32
33 // ...
34};

Next steps

This example only scratches the surface of the realtime capabilities offered by pluv.io.