NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #2686 most downloaded on npm
Coherent, zero-dependency, lazy, simple, GraphQL over WebSocket Protocol compliant server and client
Last release 10 days ago
24 Sep 2026
Release timing varies
gaps range from 9 days to 7 months
Nearly every release is documented
notes for 60 of the last 60 stable releases
16 versions withdrawn
withdrawn after publishing
9 years old
185 releases · first in 2017
Minor Changes #691 b5f53cd Thanks @niukanen1 ! - Add onPing and onPong server callbacks to ServerOptions , similar to onConnect and onDisconnect . The
b5f53cd Thanks @niukanen1! - Add onPing and onPong server callbacks to ServerOptions, similar to onConnect and onDisconnect. The callbacks receive the connection Context as the first argument and the ping/pong payload as the second, allowing apps to log or react to subprotocol-level pings with access to connection state. The automatic pong reply is preserved when using the server-level onPing callback; the low-level websocket onPing listener still disables the automatic reply for full manual control.One column per quarter.
makeHooks only registered the peer in the clients map after server.opened returned, while send / close no-op'd unless the peer was already in that map
#690 1029314 Thanks @Haasini-kudala! - Support crossws 0.4 by selecting the GraphQL WebSocket subprotocol during the upgrade handshake. Preserve automatic protocol negotiation in the legacy crossws 0.3 Node and uWebSockets adapters.
#686 536960e Thanks @cpruijsen! - Fix the CrossWS adapter ignoring socket closes issued from server.opened
makeHooks only registered the peer in the clients map after server.opened returned, while send/close no-op'd unless the peer was already in that map. A protocol-mismatch close (and any other close from inside opened) was therefore dropped, the WebSocket stayed open, and no ConnectionAck was ever sent because the message handler was never installed.
Nothing published for this version
Patch Changes #660 61731f0 Thanks @fkaempfer ! - Fix typings for WebSocketServer
61731f0 Thanks @fkaempfer! - Fix typings for WebSocketServerMinor Changes #682 1e70c1a Thanks @jwatzman ! - Add parse option for custom GraphQL parsing
The unsubscribe function returned by client.on spliced at indexOf(listener) without checking for -1 , so removing an already-removed listener would sp
#680 3fdd82f Thanks @kkhys! - Disposing of a client event listener twice no longer removes an unrelated listener
The unsubscribe function returned by client.on spliced at indexOf(listener) without checking for -1, so removing an already-removed listener would splice(-1, 1) and silently drop the most recently registered listener of the same event. This happens in practice without any double-dispose by the user: emits iterate over a copy of the listeners, so a one-shot internal listener that already unlistened itself during a nested emit (e.g. when client.terminate() is called from within a closed/error listener) is re-invoked from the copy and unlistens again, knocking out registered closed/error listeners.
Nothing published for this version
Minor Changes #672 afb7a8a Thanks @andreisergiu98 ! - Add support for graphql@17
afb7a8a Thanks @andreisergiu98! - Add support for graphql@17Nothing published for this version
Previously, when a subscription's async iterable threw an error, the server would send:
#667 fc03004 Thanks @endigma! - Fix the server sending a Complete message after an Error message for subscriptions.
Previously, when a subscription's async iterable threw an error, the server would send:
{"id":"1","type":"error","payload":[{"message":"..."}]}
{"id":"1","type":"complete"}
Per the protocol spec:
Error: This message terminates the operation and no further messages will be sent.
Complete (Server → Client): If the server dispatched the
Errormessage relative to the originalSubscribemessage, noCompletemessage will be emitted.
The server now correctly sends only the Error message:
{"id":"1","type":"error","payload":[{"message":"..."}]}
Clients that correctly follow the spec should be unaffected, as they are expected to ignore messages for operations they consider already completed.
Nothing published for this version
Nothing published for this version
It does not exist on NPM anymore and could lead to weird behavior when installing dependencies with npm . Nothing else changes, using graphql-ws with
#665 5536292 Thanks @enisdenjo! - Remove uWebSockets.js from peer dependencies in package.json
It does not exist on NPM anymore and could lead to weird behavior when installing dependencies with npm. Nothing else changes, using graphql-ws with uWebSockets.js still works.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Patch Changes #648 1f53bb4 Thanks @enisdenjo ! - Fix building issues causing CJS type definitions referencing ESM modules
1f53bb4 Thanks @enisdenjo! - Fix building issues causing CJS type definitions referencing ESM modulesNothing published for this version
Nothing published for this version
Patch Changes #630 fce94fa Thanks @m1212e ! - crossws adapter for compatibility with all supported environments
Nothing published for this version
Patch Changes #625 b4a656d Thanks @HermanBilous ! - Use Math.pow for retry delay calculation
b4a656d Thanks @HermanBilous! - Use Math.pow for retry delay calculationNothing published for this version
747c01c Thanks @enisdenjo ! - Drop ExecutionPatchResult and FormattedExecutionPatchResult types
747c01c Thanks @enisdenjo! - Drop ExecutionPatchResult and FormattedExecutionPatchResult types
Neither of the types are officially supported (yet) and the future versions of graphql-js adding support for stream/defer will a different signature for the incremental execution result.
Nothing published for this version
### Patch Changes - #621 `6b180e8` Thanks @pleunv! - FormattedExecutionResult errors field returns GraphQLFormattedError
Nothing published for this version
Deno supports ECMAScript modules exclusively.
#618 6be34c7 Thanks @enisdenjo! - Remove exports for CommonJS for Deno exports in package.json
#618 6be34c7 Thanks @enisdenjo! - Define exports for CommonJS TypeScript definitions in package.json
Nothing published for this version
Nothing published for this version
ws v7 has been deprecated. Please upgrade and use v8.
b668b30 Thanks @enisdenjo! - @fastify/websocket WebSocket in the context extra has been renamed from connection to socket
import { makeHandler } from 'graphql-ws/use/@fastify/websocket';
makeHandler({
schema(ctx) {
- const websocket = ctx.extra.connection;
+ const websocket = ctx.extra.socket;
},
context(ctx) {
- const websocket = ctx.extra.connection;
+ const websocket = ctx.extra.socket;
},
onConnect(ctx) {
- const websocket = ctx.extra.connection;
+ const websocket = ctx.extra.socket;
},
onDisconnect(ctx) {
- const websocket = ctx.extra.connection;
+ const websocket = ctx.extra.socket;
},
onClose(ctx) {
- const websocket = ctx.extra.connection;
+ const websocket = ctx.extra.socket;
},
onSubscribe(ctx) {
- const websocket = ctx.extra.connection;
+ const websocket = ctx.extra.socket;
},
onOperation(ctx) {
- const websocket = ctx.extra.connection;
+ const websocket = ctx.extra.socket;
},
onError(ctx) {
- const websocket = ctx.extra.connection;
+ const websocket = ctx.extra.socket;
},
onNext(ctx) {
- const websocket = ctx.extra.connection;
+ const websocket = ctx.extra.socket;
},
onComplete(ctx) {
- const websocket = ctx.extra.connection;
+ const websocket = ctx.extra.socket;
},
});
#613 3f11aba Thanks @enisdenjo! - Drop support for ws v7
ws v7 has been deprecated. Please upgrade and use v8.
#613 3f11aba Thanks @enisdenjo! - Drop support for deprecated fastify-websocket
fastify-websocket has been deprecated since v4.3.0.. Please upgrade and use @fastify/websocket.
#613 3f11aba Thanks @enisdenjo! - The /lib/ part from imports has been removed, for example graphql-ws/lib/use/ws becomes graphql-ws/use/ws
Simply remove the /lib/ part from your graphql-ws imports that use a handler.
- import { useServer } from 'graphql-ws/lib/use/ws';
+ import { useServer } from 'graphql-ws/use/ws';
- import { makeBehavior } from 'graphql-ws/lib/use/uWebSockets';
+ import { makeBehavior } from 'graphql-ws/use/uWebSockets';
- import { makeHandler } from 'graphql-ws/lib/use/@fastify/websocket';
+ import { makeHandler } from 'graphql-ws/use/@fastify/websocket';
- import { handleProtocols, makeHandler } from 'graphql-ws/lib/use/bun';
+ import { handleProtocols, makeHandler } from 'graphql-ws/use/bun';
- import { makeHandler } from 'https://esm.sh/graphql-ws/lib/use/deno';
+ import { makeHandler } from 'https://esm.sh/graphql-ws/use/deno';
#613 3f11aba Thanks @enisdenjo! - ErrorMessage uses and onError returns GraphQLFormattedError (instead of GraphQLError)
Thanks @benjie for working on this in #599
#613 3f11aba Thanks @enisdenjo! - Least supported Node version is v20
Node v10 has been deprecated for years now. There is no reason to support it. Bumping the engine to the current LTS (v20) also allows the code to be leaner and use less polyfills.
#613 3f11aba Thanks @enisdenjo! - Least supported graphql peer dependency is ^15.10.1 and ^16
Users are advised to use the latest of graphql because of various improvements in performance and security.
#613 3f11aba Thanks @enisdenjo! - NextMessage uses and onNext returns FormattedExecutionResult (instead of ExecutionResult)
#613 3f11aba Thanks @enisdenjo! - schema, context, onSubscribe, onOperation, onError, onNext and onComplete hooks don't have the full accompanying message anymore, only the ID and the relevant part from the message
There is really no need to pass the full SubscribeMessage to the onSubscribe hook. The only relevant parts from the message are the id and the payload, the type is useless since the hook inherently has it (onNext is next type, onError is error type, etc).
The actual techincal reason for not having the full message is to avoid serialising results and errors twice. Both onNext and onError allow the user to augment the result and return it to be used instead. onNext originally had the NextMessage argument which already has the FormattedExecutionResult, and onError originally had the ErrorMessage argument which already has the GraphQLFormattedError, and they both also returned FormattedExecutionResult and GraphQLFormattedError respectivelly - meaning, if the user serialised the results - the serialisation would happen twice.
Additionally, the onOperation, onError, onNext and onComplete now have the payload which is the SubscribeMessage.payload (SubscribePayload) for easier access to the original query as well as execution params extensions.
schemaimport { ExecutionArgs } from 'graphql';
import { ServerOptions, SubscribePayload } from 'graphql-ws';
const opts: ServerOptions = {
- schema(ctx, message, argsWithoutSchema: Omit<ExecutionArgs, 'schema'>) {
- const messageId = message.id;
- const messagePayload: SubscribePayload = message.payload;
- },
+ schema(ctx, id, payload) {
+ const messageId = id;
+ const messagePayload: SubscribePayload = payload;
+ },
};
contextimport { ExecutionArgs } from 'graphql';
import { ServerOptions, SubscribePayload } from 'graphql-ws';
const opts: ServerOptions = {
- context(ctx, message, args: ExecutionArgs) {
- const messageId = message.id;
- const messagePayload: SubscribePayload = message.payload;
- },
+ context(ctx, id, payload, args: ExecutionArgs) {
+ const messageId = id;
+ const messagePayload: SubscribePayload = payload;
+ },
};
onSubscribeimport { ServerOptions, SubscribePayload } from 'graphql-ws';
const opts: ServerOptions = {
- onSubscribe(ctx, message) {
- const messageId = message.id;
- const messagePayload: SubscribePayload = message.payload;
- },
+ onSubscribe(ctx, id, payload) {
+ const messageId = id;
+ const messagePayload: SubscribePayload = payload;
+ },
};
onOperationThe SubscribeMessage.payload is not useful here at all, the payload has been parsed to ready-to-use graphql execution args and should be used instead.
import { ExecutionArgs } from 'graphql';
import { ServerOptions, SubscribePayload, OperationResult } from 'graphql-ws';
const opts: ServerOptions = {
- onOperation(ctx, message, args: ExecutionArgs, result: OperationResult) {
- const messageId = message.id;
- const messagePayload: SubscribePayload = message.payload;
- },
+ onOperation(ctx, id, payload, args: ExecutionArgs, result: OperationResult) {
+ const messageId = id;
+ const messagePayload: SubscribePayload = payload;
+ },
};
onErrorThe ErrorMessage.payload (GraphQLFormattedError[]) is not useful here at all, the user has access to GraphQLError[] that are true instances of the error containing object references to originalErrors and other properties. The user can always convert and return GraphQLFormattedError[] by using the .toJSON() method.
import { GraphQLError, GraphQLFormattedError } from 'graphql';
import { ServerOptions, SubscribePayload } from 'graphql-ws';
const opts: ServerOptions = {
- onError(ctx, message, errors) {
- const messageId = message.id;
- const graphqlErrors: readonly GraphQLError[] = errors;
- const errorMessagePayload: readonly GraphQLFormattedError[] = message.payload;
- },
+ onError(ctx, id, payload, errors) {
+ const messageId = id;
+ const graphqlErrors: readonly GraphQLError[] = errors;
+ const subscribeMessagePayload: SubscribePayload = payload;
+ const errorMessagePayload: readonly GraphQLFormattedError[] = errors.map((e) => e.toJSON());
+ },
};
onNextThe NextMessage.payload (FormattedExecutionResult) is not useful here at all, the user has access to ExecutionResult that contains actual object references to error instances. The user can always convert and return FormattedExecutionResult by serialising the errors with GraphQLError.toJSON() method.
import { ExecutionArgs, ExecutionResult, FormattedExecutionResult } from 'graphql';
import { ServerOptions, SubscribePayload } from 'graphql-ws';
const opts: ServerOptions = {
- onNext(ctx, message, args: ExecutionArgs, result: ExecutionResult) {
- const messageId = message.id;
- const nextMessagePayload: FormattedExecutionResult = message.payload;
- },
+ onNext(ctx, id, payload, args: ExecutionArgs, result: ExecutionResult) {
+ const messageId = id;
+ const subscribeMessagePayload: SubscribePayload = payload;
+ const nextMessagePayload: FormattedExecutionResult = { ...result, errors: result.errors?.map((e) => e.toJSON()) };
+ },
};
onCompleteimport { ServerOptions, SubscribePayload } from 'graphql-ws';
const opts: ServerOptions = {
- onComplete(ctx, message) {
- const messageId = message.id;
- },
+ onComplete(ctx, id, payload) {
+ const messageId = id;
+ const subscribeMessagePayload: SubscribePayload = payload;
+ },
};
#613 3f11aba Thanks @enisdenjo! - Errors thrown from subscription iterables will be caught and reported through the ErrorMessage
Compared to the behaviour before, which terminated the whole WebSocket connection - those errors are now gracefully reported and terminate only the specific subscription that threw the error.
There's been an editorial change in the GraphQL Spec suggesting this being the correct approach.
Also, if you'd like to get involved and ideally drop your opinion about whether iterable errors should be reported as errors or ExecutionResults with errors field set, please read more here.
If you had used the suggested "ws server usage with custom subscribe method that gracefully handles thrown errors" recipe, you can simply remove it since this behaviour is now baked in.
import { subscribe } from 'graphql';
import { useServer } from 'graphql-ws/use/ws';
import { WebSocketServer } from 'ws'; // yarn add ws
const wsServer = new WebSocketServer({
port: 4000,
path: '/graphql',
});
useServer(
{
schema,
- async subscribe(...args) {
- const result = await subscribe(...args);
- if ('next' in result) {
- // is an async iterable, augment the next method to handle thrown errors
- const originalNext = result.next;
- result.next = async () => {
- try {
- return await originalNext();
- } catch (err) {
- // gracefully handle the error thrown from the next method
- return { value: { errors: [err] } };
- }
- };
- }
- return result;
- },
},
wsServer,
);
#613 3f11aba Thanks @enisdenjo! - Remove deprecated isMessage, use validateMessage instead
Replace all ocurrances of isMessage with validateMessage. Note that validateMessage throws if the message is not valid, compared with isMessage that simply returned true/false.
- import { isMessage } from 'graphql-ws';
+ import { validateMessage } from 'graphql-ws';
function isGraphQLWSMessage(val) {
- return isMessage(val);
+ try {
+ validateMessage(val);
+ return true;
+ } catch {
+ return false;
+ }
}
#613 3f11aba Thanks @enisdenjo! - Removed deprecated isFatalConnectionProblem, use shouldRetry instead
Replace all ocurrances of isFatalConnectionProblem with shouldRetry. Note that the result is inverted, where you returned false in isFatalConnectionProblem you should return true in shouldRetry.
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'ws://localhost:4000/graphql',
- isFatalConnectionProblem: () => false,
+ shouldRetry: () => true,
});
#613 3f11aba Thanks @enisdenjo! - Client is truly zero-dependency, not even a peer dependency on graphql
In non-browser environments, you can use only the client and not even depend on graphql by importing from graphql-ws/client.
import { createClient } from 'graphql-ws/client';
const client = createClient({
url: 'ws://localhost:4000/graphql',
});
Note that, in browser envirments (and of course having your bundler use the browser package.json field), you don't have to import from graphql-ws/client - simply importing from graphql-ws will only have the createClient available.
#615 29dd26a Thanks @enisdenjo! - Define optional peer dependencies and least supported versions
Using the peerDependencies in combination with peerDependenciesMeta configuration in package.json.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →