UXDL Docs

Socket.io

WebSocket server, rooms, auth, and client integration.

Socket.io handles realtime bidirectional communication — live notifications, chat, presence, and dashboard updates. It runs alongside Express on the same HTTP server.

Install

bash
pnpm add socket.io
pnpm add -D @types/cors

Server setup

typescript
// src/server.ts
import { createServer } from "http";
import { createApp } from "./app";
import { initSocket } from "./socket";
 
const PORT = process.env.PORT ?? 4000;
const app = createApp();
const httpServer = createServer(app);
 
initSocket(httpServer);
 
httpServer.listen(PORT, () => {
  console.log(`API + Socket.io on http://localhost:${PORT}`);
});
typescript
// src/socket/index.ts
import { Server } from "socket.io";
import type { Server as HttpServer } from "http";
import { socketAuth } from "./auth.middleware";
import { registerNotificationHandlers } from "./handlers/notifications.handler";
 
export function initSocket(httpServer: HttpServer) {
  const io = new Server(httpServer, {
    cors: {
      origin: process.env.CORS_ORIGIN?.split(","),
      credentials: true,
    },
    path: "/socket.io",
  });
 
  io.use(socketAuth);
 
  io.on("connection", (socket) => {
    const userId = socket.data.user.sub;
 
    // Join a personal room for targeted events
    socket.join(`user:${userId}`);
 
    registerNotificationHandlers(io, socket);
 
    socket.on("disconnect", () => {
      console.log(`Socket disconnected: ${userId}`);
    });
  });
 
  return io;
}

Socket authentication

Reuse Cognito JWT verification from the REST API:

typescript
// src/socket/auth.middleware.ts
import { Socket } from "socket.io";
import { verifyCognitoToken } from "../lib/cognito";
 
export async function socketAuth(socket: Socket, next: (err?: Error) => void) {
  const token = socket.handshake.auth.token as string;
  if (!token) return next(new Error("Authentication required"));
 
  try {
    const payload = await verifyCognitoToken(token);
    socket.data.user = { sub: payload.sub!, email: payload.email as string };
    next();
  } catch {
    next(new Error("Invalid token"));
  }
}

Event handlers

typescript
// src/socket/handlers/notifications.handler.ts
import { Server, Socket } from "socket.io";
 
export function registerNotificationHandlers(io: Server, socket: Socket) {
  socket.on("notification:read", async (notificationId: string) => {
    // Mark as read in DB
    await markNotificationRead(notificationId, socket.data.user.sub);
    socket.emit("notification:updated", { id: notificationId, read: true });
  });
}
 
// Emit from a service after a REST action
export function notifyUser(io: Server, userId: string, event: string, data: unknown) {
  io.to(`user:${userId}`).emit(event, data);
}

Client connection (Next.js)

typescript
import { io } from "socket.io-client";
 
const socket = io(process.env.NEXT_PUBLIC_API_URL!, {
  auth: { token: accessToken },
  path: "/socket.io",
});
 
socket.on("connect", () => console.log("Connected"));
socket.on("notification:new", (data) => showToast(data));

Rooms pattern

RoomPurposeJoin trigger
user:{id}Personal notificationsOn connect
project:{id}Project collaborationOn project page load
workspace:{id}Workspace-wide eventsOn workspace select

Emitting from Express services

typescript
// src/services/projects.service.ts
import { getIO } from "../socket/io-instance";
 
export async function createProject(data: CreateProjectInput) {
  const project = await db.insert(projects).values(data).returning();
 
  // Notify workspace members via Socket.io
  getIO().to(`workspace:${data.workspaceId}`).emit("project:created", project);
 
  return project;
}

Official documentation