Creating a Connection
Everything starts with one line. The constructor is where a WebSocket is born, and a few decisions you make right here — which scheme, which subprotocol — shape the entire connection. Let us take that line apart.
The constructor signature
Section titled “The constructor signature”const ws = new WebSocket(url);// or, asking for a specific application protocol:const ws = new WebSocket(url, protocols);The first argument, url, is required and must be an absolute URL using a WebSocket scheme. The second, protocols, is optional: a single string or an array of strings naming the subprotocols your client is willing to speak. That is the whole signature — one mandatory URL and one optional list.
ws vs wss
Section titled “ws vs wss”A WebSocket URL never uses http. It uses one of two dedicated schemes:
ws://— an unencrypted connection. The bytes travel in the clear, exactly like plainhttp://.wss://— the same protocol wrapped in TLS, exactly likehttps://. Encrypted, authenticated, and far harder to tamper with.
The rule in practice is simple: use wss:// for anything that is not throwaway local testing. Browsers enforce a mixed-content rule too — a page served over https:// is not allowed to open a plain ws:// connection, so on any real site wss:// is effectively mandatory.
const dev = new WebSocket('ws://localhost:8080'); // fine for local dev onlyconst prod = new WebSocket('wss://chat.example.com'); // always this in productionSubprotocols
Section titled “Subprotocols”The optional second argument lets client and server agree on which application-level protocol they will speak over the socket — think of it as choosing a shared language before the conversation starts. The browser sends your list, the server picks one, and that choice comes back on ws.protocol once the connection opens.
const ws = new WebSocket('wss://example.com', ['chat.v2', 'chat.v1']);ws.onopen = () => { console.log('server chose:', ws.protocol); // e.g. "chat.v2"};You list them in order of preference; the server is expected to honor that order. If the server supports none of them it should reject the connection. Subprotocols are optional — many apps never use them — but they are the clean way to version your wire format.
What happens the moment you construct it
Section titled “What happens the moment you construct it”Calling new WebSocket(url) does not block. It returns immediately with the object in the CONNECTING state, while the handshake runs in the background. The diagram traces those first moments:
sequenceDiagram participant App as Your code participant WS as WebSocket object participant Srv as Server App->>WS: new WebSocket(url, protocols) Note over WS: readyState = CONNECTING (returns immediately) WS->>Srv: HTTP Upgrade request (+ chosen protocols) Srv-->>WS: 101 Switching Protocols (picks one protocol) Note over WS: readyState = OPEN WS-->>App: onopen fires App->>WS: now safe to ws.send(...)
Because construction returns instantly, the object is not yet ready to send anything — readyState is still CONNECTING. You attach your handlers right after constructing, then wait for onopen. Trying to send() before that throws. We will lean on this in the next lessons.
A live connection
Section titled “A live connection”The demo uses an in-page echo socket with the real WebSocket API, so it runs anywhere with no server. It mimics the constructor’s two-argument form and reports the negotiated protocol. In a real app you would write new WebSocket('wss://your-server', ['chat.v2']) and nothing else would change.
The first log proves construction returned with readyState still 0. Only later, inside onopen, is the connection usable — and the negotiated subprotocol is available there as ws.protocol.