FluxyChat

Snippets

Snippet: Next.js end-to-end (App Router)

Goal: a copy-paste integration that:

  • mints a JWT from the Next.js backend (server-side API key)
  • renders a client page that connects to a room and sends messages (SDK)

1) .env.local (Next.js)

NEXT_PUBLIC_FLUXYCHAT_WORKER_URL="http://127.0.0.1\:8787"
FLUXY_BASE_URL="http://127.0.0.1\:8787"
FLUXY_API_KEY="fc_..."

Notes:

  • NEXT_PUBLIC_* is readable in the browser (OK for base URL).
  • Do not expose FLUXY_API_KEY to the client — server-side only.

2) Route handler to mint JWT

Create app/api/fluxy/token/route.ts:

import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const body = await request.json().catch(() => null);
  const userId = body?.userId;
  if (!userId)
    return NextResponse.json({ error: "userId required" }, { status: 400 });

  const roles = Array.isArray(body?.roles) && body.roles.length ? body.roles : ["member"];

  const res = await fetch(`${process.env.FLUXY_BASE_URL}/auth/token`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Fluxy-Api-Key": process.env.FLUXY_API_KEY!,
    },
    body: JSON.stringify({ userId, roles, ttlSeconds: 3600 }),
  });

  const json = await res.json().catch(() => ({}));
  return NextResponse.json(json, { status: res.status });
}

3) Chat page (client) with SDK

Create app/fluxy-demo/page.tsx:

"use client";

import { useEffect, useMemo, useState } from "react";
import { FluxyChatClient, useChat } from "@fluxy-chat/sdk";

export default function FluxyDemoPage() {
  const baseUrl = process.env.NEXT_PUBLIC_FLUXYCHAT_WORKER_URL!;
  const [userId, setUserId] = useState("alice");
  const [roomId, setRoomId] = useState("public-demo-room");
  const [token, setToken] = useState<string | null>(null);
  const [input, setInput] = useState("");

  useEffect(() => {
    let cancelled = false;
    async function loadToken() {
      const res = await fetch("/api/fluxy/token", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ userId, roles: ["member"] }),
      });
      const json = await res.json();
      if (!cancelled) setToken(json?.token ?? null);
    }
    loadToken().catch(() => {});
    return () => {
      cancelled = true;
    };
  }, [userId]);

  const client = useMemo(() => {
    if (!token) return null;
    return new FluxyChatClient({ baseUrl, userId, token });
  }, [baseUrl, token, userId]);

  const { messages, sendMessage, connectionStatus } = useChat({
    roomId,
    client: client ?? undefined,
  });

  return (
    <div style={{ maxWidth: 720, margin: "40px auto", fontFamily: "system-ui" }}>
      <h1>Fluxychat demo</h1>
      <p>status: {connectionStatus}</p>

      <div style={{ display: "flex", gap: 8, marginBottom: 12 }}>
        <label>
          userId{" "}
          <input value={userId} onChange={(e) => setUserId(e.target.value)} />
        </label>
        <label>
          roomId{" "}
          <input value={roomId} onChange={(e) => setRoomId(e.target.value)} />
        </label>
      </div>

      <div
        style={{
          border: "1px solid #ddd",
          borderRadius: 12,
          padding: 12,
          height: 360,
          overflow: "auto",
          marginBottom: 12,
        }}
      >
        {messages.map((m) => (
          <div key={m.id} style={{ padding: "6px 0" }}>
            <b>{m.userId}</b>: {m.content}
          </div>
        ))}
      </div>

      <form
        onSubmit={(e) => {
          e.preventDefault();
          const text = input.trim();
          if (!text) return;
          sendMessage(text);
          setInput("");
        }}
        style={{ display: "flex", gap: 8 }}
      >
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Type a message…"
          style={{ flex: 1 }}
        />
        <button type="submit" disabled={!client}>
          Send
        </button>
      </form>
    </div>
  );
}

4) Important notes

  • The room must exist and the user must be a member, otherwise WS may fail with 403.
  • In production, your backend should decide roles (not the client).

On this page