graphql-ws
6.4.1 · 15 Oct 2026MIT10.6M / weekRepositoryHomepagenpmAtom feed
6.4.1 is the newest release of 6.x.
In short
1 breaking4 removed2 new7 changed
- breaking
@fastify/websocket WebSocket in the context extra has been renamed from
connectiontosocket - removed
Drop support for
wsv7 - removed
Drop support for deprecated
fastify-websocket - removed
Remove deprecated
isMessage, usevalidateMessageinstead - removed
Removed deprecated
isFatalConnectionProblem, useshouldRetryinstead
Release note
Major Changes
- breaking
@fastify/websocket WebSocket in the context extra has been renamed from
connectiontosocket
Migrating from v5 to v6
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;
},
});- removed
Drop support for
wsv7wsv7 has been deprecated. Please upgrade and use v8. - removed
Drop support for deprecated
fastify-websocketfastify-websockethas been deprecated since v4.3.0.. Please upgrade and use@fastify/websocket. The
/lib/part from imports has been removed, for examplegraphql-ws/lib/use/wsbecomesgraphql-ws/use/ws
Migrating from v5 to v6
Simply remove the /lib/ part from your graphql-ws imports that use a handler.
ws
- import { useServer } from 'graphql-ws/lib/use/ws';
+ import { useServer } from 'graphql-ws/use/ws';uWebSockets.js
- import { makeBehavior } from 'graphql-ws/lib/use/uWebSockets';
+ import { makeBehavior } from 'graphql-ws/use/uWebSockets';@fastify/websocket
- import { makeHandler } from 'graphql-ws/lib/use/@fastify/websocket';
+ import { makeHandler } from 'graphql-ws/use/@fastify/websocket';Bun
- import { handleProtocols, makeHandler } from 'graphql-ws/lib/use/bun';
+ import { handleProtocols, makeHandler } from 'graphql-ws/use/bun';Deno
- import { makeHandler } from 'https://esm.sh/graphql-ws/lib/use/deno';
+ import { makeHandler } from 'https://esm.sh/graphql-ws/use/deno';ErrorMessageuses andonErrorreturnsGraphQLFormattedError(instead ofGraphQLError)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.
Least supported
graphqlpeer dependency is ^15.10.1 and ^16Users are advised to use the latest of
graphqlbecause of various improvements in performance and security.NextMessageuses andonNextreturnsFormattedExecutionResult(instead ofExecutionResult)schema,context,onSubscribe,onOperation,onError,onNextandonCompletehooks don't have the full accompanying message anymore, only the ID and the relevant part from the messageThere is really no need to pass the full
SubscribeMessageto theonSubscribehook. The only relevant parts from the message are theidand thepayload, thetypeis useless since the hook inherently has it (onNextisnexttype,onErroriserrortype, etc).The actual techincal reason for not having the full message is to avoid serialising results and errors twice. Both
onNextandonErrorallow the user to augment the result and return it to be used instead.onNextoriginally had theNextMessageargument which already has theFormattedExecutionResult, andonErrororiginally had theErrorMessageargument which already has theGraphQLFormattedError, and they both also returnedFormattedExecutionResultandGraphQLFormattedErrorrespectivelly - meaning, if the user serialised the results - the serialisation would happen twice.Additionally, the
onOperation,onError,onNextandonCompletenow have thepayloadwhich is theSubscribeMessage.payload(SubscribePayload) for easier access to the original query as well as execution params extensions.
Migrating from v5 to v6
schema
import { 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;
+ },
};context
import { 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;
+ },
};onSubscribe
import { 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;
+ },
};onOperation
The 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;
+ },
};onError
The 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());
+ },
};onNext
The 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()) };
+ },
};onComplete
import { 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;
+ },
};Errors thrown from subscription iterables will be caught and reported through the
ErrorMessageCompared 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 witherrorsfield set, please read more here.
Migrating from v5 to v6
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,
);- removed
Remove deprecated
isMessage, usevalidateMessageinstead
Migrating from v5 to v6
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;
+ }
}- removed
Removed deprecated
isFatalConnectionProblem, useshouldRetryinstead
Migrating from v5 to v6
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,
});Minor Changes
Client is truly zero-dependency, not even a peer dependency on
graphqlIn non-browser environments, you can use only the client and not even depend on
graphqlby importing fromgraphql-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
browserpackage.json field), you don't have to import fromgraphql-ws/client- simply importing fromgraphql-wswill only have thecreateClientavailable.Define optional peer dependencies and least supported versions
Using the
peerDependenciesin combination withpeerDependenciesMetaconfiguration inpackage.json.