← Back to portfolio

Selected work

StreamFlow

A real-time streaming platform with integrated chat, moderation, and low-latency playback.

Summary

StreamFlow brings live streaming and chat into a single experience where moderation stays connected to the broadcast.

Built for creators who want a lighter setup than full broadcast platforms.

Problem

Live streaming tools split chat, moderation, and playback across different apps. Real-time coordination becomes harder than it should be.

Solution

Chat and live viewing in one thread so the audience and host stay aligned without switching tools. WebSocket-driven for low latency.

Features

Live chatMessages flow in the same place as the stream with sub-second delivery.
Moderation hooksHosts can manage chat flow without losing focus on the broadcast.
WebSocket syncSocket.IO provides near-real-time state updates between viewers, chat, and playback.
Replay supportChat history persists so post-stream review stays connected to context.

Architecture

Stack: React · Node.js · Socket.IO · Express · MongoDB · Bun

ReactReact
Socket.IOSocket.IO
Node.jsNode.js
MongoDBMongoDB
BunBun

React client connects to the server via Socket.IO for real-time events.

Server broadcasts chat messages and stream state to all connected clients.

MongoDB stores chat history, room configs, and moderation logs.

Bun powers the server runtime for faster cold starts.

WebSockets were essential here — polling would add unacceptable latency for a live experience.

Key code

server/socket.js
io.on("connection", (socket) => {
  socket.on("join-room", async ({ roomId, username }) => {
    socket.join(roomId);
    const history = await db.messages.find({ roomId })
      .sort({ createdAt: -1 }).limit(50).lean();
    socket.emit("chat-history", history.reverse());
    io.to(roomId).emit("user-joined", { username, count: getRoomCount(roomId) });
  });

  socket.on("send-message", async ({ roomId, username, text }) => {
    const msg = await db.messages.create({ roomId, username, text });
    io.to(roomId).emit("new-message", {
      id: msg._id, username: msg.username, text: msg.text, createdAt: msg.createdAt
    });
  });

  socket.on("moderate", ({ roomId, messageId, action }) => {
    if (action === "delete") {
      db.messages.deleteById(messageId);
      io.to(roomId).emit("message-deleted", { messageId });
    }
  });
});

Results

Interaction model is much clearer than a chat widget beside a stream.

Lesson: event state needs to be explicit from the start if moderation is part of the flow.