Introduction

Have you ever watched those live collaboration tools where you can see someone typing in real time and thought, "How does that work?" It's all about WebSockets, specifically Socket.IO.

Why do we need WebSockets?

Traditional web apps are like sending letters back and forth - you request something, wait for a response, and repeat. Not exactly the stuff of lightning-fast experiences, right?

Enter WebSockets: imagine having a phone call instead of sending letters. Both sides can talk whenever they want without waiting for the other to finish. That's the game-changer WebSockets bring to web development.

But raw WebSockets can be... finicky. That's why most developers reach for Socket.IO. It handles all the annoying stuff like:

  • Automatically reconnecting when connections drop (because networks are never perfect)
  • Broadcasting messages to multiple clients (essential for chat apps)
  • Creating rooms and namespaces (think private vs. public chats)
  • Adding middleware for things like authentication

Plus, it gracefully falls back to other methods when WebSockets aren't supported.

When Should You Actually Use Websockets?

Before we dive into code, let's be honest - not every app needs WebSockets. They're perfect when:

  • Users need instant updates (think stock prices or sports scores)
  • Communication flows both ways (like chat apps)
  • Every millisecond of latency matters (multiplayer games)
  • Your architecture is event-driven

If you're just building a blog or simple CRUD app, traditional HTTP requests will serve you just fine and save you some complexity.

Project Time!!!

Alright, enough theory - let's get our hands dirty! We'll create a simple real-time chat app with a Node.js backend and a React frontend.

Setting Up Our Backend

First, let's create a simple Socket.IO server:

mkdir socket-server
cd socket-server
npm init -y
npm install express socket.io cors

Now, let's create our index.js file:

const express = require('express');
const http = require('http');
const cors = require('cors');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server, {
  cors: {
    origin: 'http://localhost:5173', // Vite's default port
    methods: ['GET', 'POST']
  }
});

io.on('connection', (socket) => {
  console.log(`User connected: ${socket.id}`);

  socket.on('send_message', (data) => {
    io.emit('receive_message', data);
  });

  socket.on('disconnect', () => {
    console.log('User disconnected');
  });
});

server.listen(3001, () => {
  console.log('Server is running on port 3001');
});

Pretty simple, right? When someone connects, we log it. When they send a message, we broadcast it to everyone. When they disconnect, we log that too.

Creating Our Frontend

Now for the fun part - create our React app using Vite:

npm create vite@latest socket-client --template react-ts
cd socket-client
npm install
npm install socket.io-client

Let's create a reusable socket connection in src/socket.ts:

import { io } from 'socket.io-client';

const socket = io('http://localhost:3001');
export default socket;

And now for our chat interface in src/App.tsx:

import { useEffect, useState } from 'react';
import socket from './socket';

interface Message {
  message: string;
  author: string;
}

function App() {
  const [message, setMessage] = useState('');
  const [author, setAuthor] = useState('');
  const [messages, setMessages] = useState<Message[]>([]);

  useEffect(() => {
    socket.on('receive_message', (data: Message) => {
      setMessages((prev) => [...prev, data]);
    });

    return () => {
      socket.off('receive_message');
    };
  }, []);

  const sendMessage = () => {
    if (message && author) {
      socket.emit('send_message', { message, author });
      setMessage('');
    }
  };

  return (
    <div style={{ padding: '2rem' }}>
      <h2>Chat Apph2>
      <input
        type="text"
        placeholder="Enter Name"
        value={author}
        onChange={(e) => setAuthor(e.target.value)}
      />
      <input
        type="text"
        placeholder="Enter Message"
        value={message}
        onChange={(e) => setMessage(e.target.value)}
        onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
      />
      <button onClick={sendMessage}>Send Messagebutton>
      <div>
        {messages.map((msg, index) => (
          <div key={index}>
            <strong>{msg.author}:strong> {msg.message}
          div>
        ))}
      div>
    div>
  );
}

export default App;

Let's see the app in action

Let's fire everything up:

For the backend:

node index.js

For the frontend:

npm run dev

Now open http://localhost:5173 in a couple of browser tabs and start chatting. Watch as messages appear instantly across all tabs - that's the WebSocket magic happening!

What's Next?

This simple example just scratches the surface. Once you get comfortable with the basics, you might want to add:

  • Private messaging rooms
  • User authentication
  • Message persistence with a database
  • Typing indicators ("Sarah is typing...")
  • Read receipts

The real-time possibilities are endless, and now you have the foundation to build them!

Conclusion

Whether you're creating the next big multiplayer game, a collaborative document editor, or just a super-responsive dashboard, Socket.IO and React make a powerful combo for bringing real-time features to life. If you like this blog and want to learn more about Frontend Development and Software Engineering, you can follow me on Dev.to.