Socket.IO in Teamwise
Step-by-step Guide
This section explains the socket flow in Teamwise from first principles. It is a hands-on guide for beginners and for developers who want to add custom socket logic. Short, copy-paste-ready snippets are included. Packages are already bundled with Teamwise, so npm install in each folder installs everything you need.
Quick overview:
Frontend opens a socket connection. Backend accepts it, places users in rooms, and broadcasts
events.
The pattern is: connect → authenticate → join rooms → emit/listen → cleanup.
Prerequisites
-
Node.js + npm installed.
-
Environment variable for socket URL: e.g. VITE_SOCKET_URL (frontend) and server URL on backend.
-
Teamwise bundle already includes socket.io (backend) and socket.io-client (frontend). No extra install required beyond npm install.
Quick overview:
Create a single socket instance that you can reuse across the app.
- // src/services/socket.ts
- import { io } from "socket.io-client";
- export const socket = io(import.meta.env.VITE_SOCKET_URL, {
- transports: ["polling"], // fallback-first; can enable 'websocket' if needed
- reconnectionAttempts: 3,
- reconnectionDelay: 1000,
- timeout: 30000,
- });
Notes:
-
-autoConnect: false lets you attach auth before connecting.
-
-transports controls fallback behavior.
-
-reconnection* settings tune reliability.
Frontend: connect and authenticate
Wrap your app with a SocketProvider that connects when the user logs in and disconnects on logout.
- // src/providers/SocketProvider.tsx (concept)
- useEffect(() => {
- if (!user || !token) return;
- socket.auth = { token }; // pass token for server validation
- socket.connect();
- socket.emit("join_room", user.id);
- return () => {
- socket.disconnect();
- };
- }, [user, token]);
Tips:
-
-Set socket.auth or include token in a query string for server-side validation.
-
-Always remove listeners and stop intervals in cleanup.
Frontend: listening and emitting events
Use small handler functions and remove them on cleanup.
- // basic listener
- socket.on("receive_message", (msg) => {
- // update redux or react-query cache
- });
- // emit a message
- socket.emit("send_message", { channelId, text });
- Cleanup:
- socket.off("receive_message");
Recommended pattern: centralize socket handlers in a hook (e.g., useSocketHandlers) so logic is testable and maintainable.
Backend: minimal server setup
Start a Socket.IO server and handle basic events.
- // server.js (concept)
- const httpServer = require("http").createServer(app);
- const { Server } = require("socket.io");
- const io = new Server(httpServer, { cors: { origin: "*" } });
- io.on("connection", (socket) => {
- console.log("connected", socket.id);
- socket.on("join_room", (userId) => {
- socket.join(`user_${userId}`); // personal room
- });
- socket.on("send_message", async (payload) => {
- // persist message in DB
- const recipientRoom = `user_${payload.recipientId}`;
- io.to(recipientRoom).emit("receive_message", payload);
- });
- });
Notes:
-
-Use socket.join(room) to target users or channels.
-
-io.to(room).emit(...) broadcasts to a room.
-
-Validate everything server-side (auth, payload shape, permissions).
Common event map
Use consistent, descriptive event names. Example mapping:
-
-Client → Server
-
-join_room (userId)
-
-request_status_update
-
-send_message (message payload)
-
-typing:start / typing:stop
-
-mark_seen (messageId)
-
-Server → Server
-
receive_message
-
message_status_updated
-
user_status_update
-
channel_added / channel_deleted
-
member_added / member_left
Keep this list in your constants so both sides match exactly.
Presence and heartbeat
Periodically emit a heartbeat so the server can maintain accurate presence.
- // client-side
- const heartbeat = setInterval(() => {
- if (socket.connected) socket.emit("request_status_update");
- }, 5 * 60 * 1000); // every 5 minutes
- Cleanup on unmount or logout:
- clearInterval(heartbeat);
Server should update last-seen and broadcast user_status_update to relevant rooms.
Reliability and reconnection
Tune options on client and handle connect_error:
- socket.on("connect_error", (err) => {
- console.warn("socket error", err);
- });
Client options you can tweak:
-reconnectionAttempts, reconnectionDelay, timeout, rememberUpgrade.
Server side, gracefully handle disconnects and re-joins. If you scale horizontally, use an adapter.
Security best practices
-
-Authenticate connections. Validate JWT tokens in socket.handshake.auth or in a middleware.
-
-Authorize every action. Do not trust client-sent IDs. Check permissions against user record.
-
-CORS: explicitly configure allowed origins.
-
Rate-limit or throttle events that can be spammed.
-
Avoid sensitive payloads over sockets without encryption. Use HTTPS/WSS.
-
Sanitize user-generated content on server side.
Adding custom logic
Typing Indicator
- // client
- socket.emit("typing:start", { chatId });
- // server forwards
- socket.to(`channel_${chatId}`).emit("typing:start", { userId });
Upload progress
-
-Client uploads directly to server or presigned S3.
-
-Emit upload:progress with percent to channel so recipients can show progress.
Integrating with app state
-
-Update local UI via Redux or React Query on socket events.
-
-Use queryClient.setQueryData(...) to insert new messages into the paged cache
-
-Keep UI updates idempotent. Socket events may arrive in any order.
Debugging & testing tips
-
-Open browser console: socket.connected, socket.id, socket.io.engine.transport.name.
-
-Check connect_error messages.
-
-Use server logs to see joins and emits.
-
For quick local tests use socket.io-client in node REPL or simple HTML page.
Troubleshooting (common issues)
-
-Not connecting: check URL, CORS, and transports mismatch.
-
-Events not received: confirm event name matches and room membership.
-
-Duplicate messages: ensure idempotency and server message dedupe.
-
-Auth fails: verify token is passed and validated.
Quick checklist before adding custom socket features
-
-Event name added to shared constants.
-
-Server validates the event payload and permissions.
-
-Client registers and cleans up listeners.
-
-UI updates via state management are idempotent.
-
-Tests for edge-cases (offline, reconnect, multi-tab).
-
-Consider scaling needs and adapters if required.
Final notes
-
-Socket.IO is included in the bundle. Run npm install in frontend and backend; no separate install required.
-
-Default flow is ready-to-use. To add custom features, follow connect → authenticate → join → emit/listen → cleanup.
-
-Keep events centralized in a constants file. That reduces breakage and eases onboarding
-
-UI updates via state management are idempotent.
-
-Tests for edge-cases (offline, reconnect, multi-tab).
-
-Consider scaling needs and adapters if required.
What's Next?
Let’s get started — your team’s new home is Teamwise Chat!