DEV Community

Safal Bhandari
Safal Bhandari

Posted on

Understanding Web Socket

Detailed explanation with flow:


1. What happens normally with Express

When you write:

import express from 'express';
const app = express();
app.listen(3000);
Enter fullscreen mode Exit fullscreen mode
  • Express automatically creates an internal HTTP server using Node’s http module.
  • You don’t see it, but internally it does:
  const http = require('http');
  const server = http.createServer(app);
  server.listen(3000);
Enter fullscreen mode Exit fullscreen mode
  • This works fine for normal HTTP requests (GET, POST, etc.).
  • However, WebSockets are not normal HTTP. They start as HTTP and then upgrade to a persistent TCP connection.

2. How WebSockets work under the hood

When a client connects via:

const ws = new WebSocket('ws://localhost:3000');
Enter fullscreen mode Exit fullscreen mode

The browser doesn’t immediately open a WebSocket. It first sends an HTTP request like this:

GET / HTTP/1.1
Host: localhost:3000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: abc123==
Sec-WebSocket-Version: 13
Enter fullscreen mode Exit fullscreen mode

This is called an HTTP Upgrade request.
The server must respond properly (with special headers) to switch the protocol from HTTP → WebSocket.

But Express does not know how to handle this kind of upgrade request — it’s built only for normal HTTP traffic.


3. Why you need createServer(app)

To handle this “Upgrade” event, you must access the raw Node HTTP server.
That’s why you write:

import { createServer } from 'http';
const server = createServer(app);
Enter fullscreen mode Exit fullscreen mode

Now server is the actual low-level HTTP server that Express is attached to.
This gives you full control over its events, including:

  • 'request' — normal HTTP requests (handled by Express)
  • 'upgrade' — special WebSocket handshake requests

You couldn’t access the 'upgrade' event if you just did app.listen().


4. Why you need WebSocketServer

The ws library provides a class called WebSocketServer that knows how to:

  • Detect upgrade requests
  • Perform the WebSocket handshake
  • Maintain open socket connections
  • Handle message, close, etc.

You attach it to your existing HTTP server:

const wss = new WebSocketServer({ server });
Enter fullscreen mode Exit fullscreen mode

This tells the ws library:

“Listen to this HTTP server. Whenever you see an Upgrade request, turn it into a WebSocket connection.”

So your one server now supports both:

  • Express (normal HTTP routes)
  • WebSockets (real-time communication)

5. Full flow diagram

Browser ───HTTP GET───> Express app (normal request)
        ───Upgrade req─> Node HTTP server (detected by ws)
                        ↓
                    WebSocketServer
                        ↓
              Bi-directional socket open
Enter fullscreen mode Exit fullscreen mode

6. Code summary

import express from 'express';
import { createServer } from 'http';
import { WebSocketServer } from 'ws';

const app = express();
const server = createServer(app); // step 1: expose HTTP server

const wss = new WebSocketServer({ server }); // step 2: attach WS to it

app.get('/', (req, res) => res.send('Hello HTTP'));

wss.on('connection', (socket) => {
  console.log('New WebSocket connection');
  socket.send('Hello WebSocket');
});

server.listen(3000, () => console.log('Server running on port 3000'));
Enter fullscreen mode Exit fullscreen mode

In short:

Component Purpose
createServer(app) Gives you control of the real HTTP server so you can handle upgrades
WebSocketServer({ server }) Handles WebSocket handshakes and keeps live connections
Express Handles normal HTTP routes
WebSocket Handles persistent two-way communication

Top comments (0)