# ws: the Node.js WebSocket client and server, and when it is the wrong pick

> ws is a pure JavaScript implementation of RFC 6455 for Node.js, published as the npm package ws. It gives you a WebSocket client and server with no browser dependency, and it is the layer most other Node WebSocket wrappers build on.

**websockets/ws** — Simple to use, blazing fast and thoroughly tested WebSocket client and server for Node.js

- Repository: https://github.com/websockets/ws
- Stars: 22,806 · Forks: 2,612
- Language: JavaScript
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/websockets-ws

## What ws solves, and who is actually supposed to use it

Node.js has an HTTP server but no WebSocket server. ws fills that gap: it implements RFC 6455 in JavaScript, exposing a WebSocketServer class for the server side and a WebSocket class for the client side. The README describes it as "a simple to use, blazing fast, and thoroughly tested WebSocket client and server implementation", and the package keywords list RFC-6455 and real-time, so the intent is protocol work, not application scaffolding.

The audience is backend developers. The README states plainly that the module does not work in the browser, and clarifies that when the docs say client they mean a backend process acting as a client in the WebSocket conversation. Browser code must use the native WebSocket object. For code that has to run in both places, the README points at wrappers on npm such as isomorphic-ws rather than pretending ws is portable.

That boundary is the useful part. If you are building a Node service that pushes events to connected clients, or a Node process that opens an outbound WebSocket to someone else's endpoint, ws is aimed at you. If you are writing frontend code, it is not.

## How the client and server objects fit together

There are two entry points. On the server, WebSocketServer takes either a port or an existing HTTP/S server and emits a connection event carrying a per-connection socket object. On that socket you attach message, error and close handlers. On the client, the WebSocket constructor takes a URL and emits open, message, error and close. Both sides speak the same frame format, which is why the same event names appear in the README's examples for each.

The repository is organised around that split: index.js is the package main, lib/ holds the implementation, wrapper.mjs provides the ESM entry, and the exports map in package.json routes browser builds to browser.js, import to wrapper.mjs and require to index.js. There is a doc/ directory with Node.js-style API documentation, a test/ directory, and a bench/ directory. The README also notes the project passes the Autobahn test suite for both server and client, which is the standard conformance suite for the protocol.

Two optional native addons sit alongside the pure JavaScript path. bufferutil improves masking and unmasking of frame payloads, and utf-8-validate provides a polyfill for buffer.isUtf8() on Node versions prior to v18.14.0. Both are declared as optional peer dependencies in package.json, so they are not pulled in unless you ask for them.

## Installing ws and running a first server

The install step is a single npm command, and the README gives it without qualification:

```bash
npm install ws
```

A minimal server binds a port and reacts to connections. The README's simple server example creates a WebSocketServer on port 8080 and logs whatever arrives:

```js
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.on('error', console.error);

  ws.on('message', function message(data) {
    console.log('received: %s', data);
  });
});
```

After starting that file, a client connecting to ws://localhost:8080 should cause the server process to print the received payload. Note the error handler on the socket: the README includes one in every example, and leaving it out means a socket-level failure surfaces as an unhandled error event.

The client side is symmetrical. This example opens a connection, sends a string on open, and logs the reply:

```js
import WebSocket from 'ws';

const ws = new WebSocket('ws://www.host.com/path');

ws.on('error', console.error);

ws.on('open', function open() {
  ws.send('something');
});

ws.on('message', function message(data) {
  console.log('received: %s', data);
});
```

ws also accepts typed arrays directly. The README's binary example builds a Float32Array and passes it to ws.send, with no manual serialisation step.

If you want the optional speed-ups, the README documents them as opt-in installs:

```bash
npm install --save-optional bufferutil
```

Prebuilt binaries exist for the most popular platforms, so a C++ compiler is not necessarily required. To stop ws from loading bufferutil, set the WS_NO_BUFFER_UTIL environment variable; the README notes this can be useful where another user can place a package on your resolver path.

## permessage-deflate is off on the server for a reason

ws supports the permessage-deflate extension, and the default is asymmetric: disabled on the server, enabled on the client. The README is direct about why, stating the extension "adds a significant overhead in terms of performance and memory consumption" and suggesting you enable it only if it is really needed.

The README goes further than most projects would. It warns that Node.js has known issues with high-performance compression, where increased concurrency, especially on Linux, can lead to what it calls catastrophic memory fragmentation and slow performance, linking to a Node zlib bug. Its recommendation is to build a test representative of your own workload before running permessage-deflate in production. That is a real constraint, not a footnote.

If you do enable it, the README shows the tuning surface: zlibDeflateOptions and zlibInflateOptions are passed into the raw deflate and inflate streams, and options such as clientNoContextTakeover, serverNoContextTakeover, serverMaxWindowBits, concurrencyLimit and threshold control negotiation and behaviour. The threshold option sets the size in bytes below which messages are not compressed when context takeover is disabled, and the README's example uses 1024.

A client can refuse compression outright by passing perMessageDeflate: false, and the README notes the client only uses the extension when the server supports and enables it. So a server that leaves the default alone will not compress, regardless of what the client would accept.

## Where ws is the wrong tool

The browser boundary is the first hard limit. The README states the module does not work in the browser and directs browser clients to the native WebSocket object. Any plan that assumes one file runs on both sides needs a wrapper such as isomorphic-ws, which is a dependency you are choosing to add.

The second limit is that ws is a protocol implementation, not a framework. There is no room abstraction, no reconnection policy, no message acknowledgement, no channel multiplexing. The README's own FAQ asks how to detect and close broken connections, which tells you the library does not do it for you: you are expected to build a ping/pong or timeout strategy yourself. The same applies to retrieving a client's IP address, which the FAQ also covers as something you have to work out.

The third is operational. The README's opt-in section mentions the WS_NO_BUFFER_UTIL variable in the context of systems where a user can place a package in another application's search path. That is a statement about the Node resolver, and it means a deployment that installs optional native addons inherits that consideration. If your environment cannot tolerate it, the environment variables exist, but you have to know to set them.

Finally, engines in package.json specifies node >=10.0.0. That is the floor ws supports, not a recommendation, and the utf-8-validate path is described as legacy for versions prior to v18.14.0.

## ws against Socket.IO and the browser-native WebSocket

The most common alternative cited in practice is Socket.IO, and the difference is architectural rather than incremental. Socket.IO is a higher-level layer with its own protocol on top of WebSocket, offering named events, rooms and automatic reconnection. ws implements RFC 6455 and stops there. Choosing Socket.IO means accepting its client library on the other end and its framing on the wire; choosing ws means any RFC 6455 client can connect, including the browser's native WebSocket and clients written in other languages.

That interoperability is the real dividing line. The README's note that browser clients must use the native WebSocket object is not a limitation to work around, it is a statement that ws speaks a standard the browser already speaks. A Socket.IO server cannot serve a plain browser WebSocket connection without the Socket.IO client.

The other alternative is the native WebSocket in the browser itself, which is not a competitor on the server side but is the correct tool on the client side. The README treats it that way. If your architecture is browser talks to Node, you will likely end up with native WebSocket in the frontend and ws in the backend, and the wire format is the same on both ends.

## Maintenance, versioning and the MIT licence

The repository is not archived, and the last push was on 2026-09-04. Releases are frequent and versioned in a way worth reading carefully: 8.21.3 on 2026-08-06 and 8.21.2 on 2026-08-03 are on the current line, while 7.5.13 on 2026-07-17 shows the 7.x branch still receiving releases. If you are pinned to 7.x, that line is alive, but it is not where new work lands.

ws is MIT licensed. In practical terms that permits use, modification and redistribution provided the copyright notice and permission notice are retained, and it comes without warranty. That is a permissive licence compatible with commercial closed-source use, but the LICENSE file is the authority and this is not legal advice.

The upgrade cost is mostly about the optional addons and the compression defaults. bufferutil and utf-8-validate are optional peer dependencies, so a version bump can change what gets loaded without changing your code. The README documents WS_NO_BUFFER_UTIL and WS_NO_UTF_8_VALIDATE as the escape hatches, and pinning those in your environment is cheaper than discovering a native build failure during a deploy. The permessage-deflate defaults are the other thing to re-check: a server that never enabled compression stays uncompressed across upgrades, but a client that relies on the default being enabled will negotiate differently against a server that changes its settings.

## Conclusion

Adopt ws when you need a WebSocket endpoint or client inside a Node.js process and you want the protocol handling rather than a framework on top of it. Skip it if your code runs in a browser, if you want rooms and reconnection built in, or if you are not on Node.js at all. Verify first that your Node version satisfies the engines field (>=10.0.0 in package.json), that your proxy forwards Upgrade headers, and whether you actually want perMessageDeflate, which is off on the server by default and costs memory when enabled.

## FAQ

### What is the difference between a WebSocket (WS) and a WebSocket (WSS)?

ws accepts ws:// and wss:// URLs, and the repository includes an SSL example under examples/ssl.js for the secure case. The difference is the transport underneath: ws:// is plain and wss:// runs over TLS. The README does not spell out the distinction, so treat the scheme as you would for HTTP versus HTTPS.

### Is ws over HTTP?

The README shows WebSocketServer accepting an existing HTTP/S server and includes examples for an external HTTP/S server and for multiple servers sharing a single HTTP/S server. So ws can attach to an HTTP server rather than replacing it, which is how the upgrade handshake is usually wired.

### What does "ws server" mean in this project?

In ws it means the WebSocketServer class, which the README instantiates with either a port or an existing server and which emits a connection event per client socket. It is the server half of the RFC 6455 implementation, not a hosted service.

## Sources

- [Issues](https://github.com/websockets/ws/issues)
- [License: MIT](https://github.com/websockets/ws/blob/master/LICENSE)
- [README](https://github.com/websockets/ws/blob/master/README.md)
- [Releases](https://github.com/websockets/ws/releases)
- [websockets/ws on GitHub](https://github.com/websockets/ws)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/websockets-ws
