Getting started
Install
$ yarn add graphql-ws
Create a GraphQL schema
import { buildSchema } from 'graphql';
const schema = buildSchema(`
type Query {
hello: String
}
type Subscription {
greetings: String
}
`);
const roots = {
query: {
hello: () => 'Hello World!',
},
subscription: {
greetings: async function* sayHiIn5Languages() {
for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) {
yield { greetings: hi };
}
},
},
};
Start the server
import https from 'https';
import { execute, subscribe } from 'graphql';
import { createServer } from 'graphql-ws';
const server = https.createServer(function weServeSocketsOnly(_, res) {
res.writeHead(404);
res.end();
});
createServer(
{
schema,
roots,
execute,
subscribe,
},
{
server,
path: '/graphql',
},
);
server.listen(443);
Use the client
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'wss://welcomer.com/graphql',
});
(async () => {
const result = await new Promise((resolve, reject) => {
let result;
client.subscribe(
{
query: '{ hello }',
},
{
next: (data) => (result = data),
error: reject,
complete: () => resolve(result),
},
);
});
expect(result).toEqual({ hello: 'Hello World!' });
})();
(async () => {
const onNext = () => {
};
await new Promise((resolve, reject) => {
client.subscribe(
{
query: 'subscription { greetings }',
},
{
next: onNext,
error: reject,
complete: resolve,
},
);
});
expect(onNext).toBeCalledTimes(5);
})();
Recipes
Client usage with Promise
import { createClient, SubscribePayload } from 'graphql-ws';
const client = createClient({
url: 'wss://hey.there/graphql',
});
async function execute<T>(payload: SubscribePayload) {
return new Promise((resolve, reject) => {
let result: T;
client.subscribe<T>(payload, {
next: (data) => (result = data),
error: reject,
complete: () => resolve(result),
});
});
}
(async () => {
try {
const result = await execute({
query: '{ hello }',
});
} catch (err) {
}
})();
Client usage with Observable
import { Observable } from 'relay-runtime';
import { Observable } from '@apollo/client';
import { Observable } from 'rxjs';
import Observable from 'zen-observable';
const client = createClient({
url: 'wss://graphql.loves/observables',
});
function toObservable(operation) {
return new Observable((observer) => client.subscribe(operation, observer));
}
const observable = toObservable({ query: `subscription { ping }` });
const subscription = observable.subscribe({
next: (data) => {
expect(data).toBe({ data: { ping: 'pong' } });
},
});
subscription.unsubscribe();
Client usage with Relay
import {
Network,
Observable,
RequestParameters,
Variables,
} from 'relay-runtime';
import { createClient } from 'graphql-ws';
const subscriptionsClient = createClient({
url: 'wss://i.love/graphql',
connectionParams: () => {
const session = getSession();
if (!session) {
return {};
}
return {
Authorization: `Bearer ${session.token}`,
};
},
});
function fetchOrSubscribe(operation: RequestParameters, variables: Variables) {
return Observable.create((sink) => {
if (!operation.text) {
return sink.error(new Error('Operation text cannot be empty'));
}
return subscriptionsClient.subscribe(
{
operationName: operation.name,
query: operation.text,
variables,
},
{
...sink,
error: (err) => {
if (err instanceof Error) {
sink.error(err);
} else if (err instanceof CloseEvent) {
sink.error(
new Error(
`Socket closed with event ${err.code}` + err.reason
? `: ${err.reason}`
: '',
),
);
} else {
sink.error(new Error(err.map(({ message }) => message).join(', ')));
}
},
},
);
});
}
export const network = Network.create(fetchOrSubscribe, fetchOrSubscribe);
Client usage with Apollo
import { ApolloLink, Operation, FetchResult, Observable } from '@apollo/client';
import { createClient, Config, Client } from 'graphql-ws';
class WebSocketLink extends ApolloLink {
private client: Client;
constructor(config: Config) {
super();
this.client = createClient(config);
}
public request(operation: Operation): Observable<FetchResult> {
return new Observable((sink) => {
return this.client.subscribe<FetchResult>(operation, {
...sink,
error: (err) => {
if (err instanceof Error) {
sink.error(err);
} else if (err instanceof CloseEvent) {
sink.error(
new Error(
`Socket closed with event ${err.code}` + err.reason
? `: ${err.reason}`
: '',
),
);
} else {
sink.error(new Error(err.map(({ message }) => message).join(', ')));
}
},
});
});
}
}
const link = new WebSocketLink({
url: 'wss://where.is/graphql',
connectionParams: () => {
const session = getSession();
if (!session) {
return {};
}
return {
Authorization: `Bearer ${session.token}`,
};
},
});
Client usage in Node
const WebSocket = require('ws');
const Crypto = require('crypto');
const { createClient } = require('graphql-ws');
const client = createClient({
url: 'wss://no.browser/graphql',
webSocketImpl: WebSocket,
generateID: () =>
([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
(c ^ (Crypto.randomBytes(1)[0] & (15 >> (c / 4)))).toString(16),
),
});
Server usage with Express GraphQL
import https from 'https';
import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { createServer } from 'graphql-ws';
import { execute, subscribe } from 'graphql';
import { schema } from 'my-graphql-schema';
const app = express();
app.use('/graphql', graphqlHTTP({ schema }));
const server = https.createServer(app);
server.listen(443, () => {
createServer(
{
schema,
execute,
subscribe,
},
{
server,
path: '/graphql',
},
);
});
Server usage with console logging
import https from 'https';
import { execute, subscribe } from 'graphql';
import { createServer } from 'graphql-ws';
const server = https.createServer(function weServeSocketsOnly(_, res) {
res.writeHead(404);
res.end();
});
createServer(
{
schema,
onConnect: (ctx) => {
console.log('Connect', ctx);
},
onSubscribe: (ctx, msg) => {
console.log('Subscribe', { ctx, msg });
},
onNext: (ctx, msg, args, result) => {
console.debug('Next', { ctx, msg, args, result });
},
onError: (ctx, msg, errors) => {
console.error('Error', { ctx, msg, errors });
},
onComplete: (ctx, msg) => {
console.log('Complete', { ctx, msg });
},
},
{
server,
path: '/graphql',
},
);
server.listen(443);
Server usage on a multi WebSocket server
import https from 'https';
import WebSocket from 'ws';
import url from 'url';
import { execute, subscribe } from 'graphql';
import { createServer, createClient } from 'graphql-ws';
import { schema } from 'my-graphql-schema';
const server = https.createServer(function weServeSocketsOnly(_, res) {
res.writeHead(404);
res.end();
});
const waveWS = new WebSocket.Server({ noServer: true });
const graphqlWS = new WebSocket.Server({ noServer: true });
server.on('upgrade', (request, socket, head) => {
const pathname = url.parse(request.url).pathname;
if (pathname === '/wave') {
waveWS.handleUpgrade(request, socket, head, (client) => {
waveWS.emit('connection', client, request);
});
} else if (pathname === '/graphql') {
graphqlWS.handleUpgrade(request, socket, head, (client) => {
graphqlWS.emit('connection', client, request);
});
} else {
socket.destroy();
}
});
waveWS.on('connection', (socket) => {
socket.send('🌊');
});
createServer(
{
schema,
execute,
subscribe,
},
graphqlWS,
);
server.listen(443);
Server usage with custom static GraphQL arguments
import { validate, execute, subscribe } from 'graphql';
import { createServer } from 'graphql-ws';
import { schema, roots, getStaticContext } from 'my-graphql';
createServer(
{
context: getStaticContext(),
schema,
roots,
execute,
subscribe,
},
{
server,
path: '/graphql',
},
);
Server usage with custom dynamic GraphQL arguments and validation
import { parse, validate, execute, subscribe } from 'graphql';
import { createServer } from 'graphql-ws';
import { schema, getDynamicContext, myValidationRules } from 'my-graphql';
createServer(
{
execute,
subscribe,
onSubscribe: (ctx, msg) => {
const args = {
schema,
contextValue: getDynamicContext(ctx, msg),
operationName: msg.payload.operationName,
document: parse(msg.payload.operationName),
variableValues: msg.payload.variables,
};
const errors = validate(args.schema, args.document, myValidationRules);
if (errors.length > 0) {
return errors;
}
return args;
},
},
{
server,
path: '/graphql',
},
);
Server and client usage with persisted queries
import { parse, execute, subscribe } from 'graphql';
import { createServer } from 'graphql-ws';
import { schema } from 'my-graphql-schema';
type QueryID = string;
const queriesStore: Record<QueryID, ExecutionArgs> = {
iWantTheGreetings: {
schema,
document: parse('subscription Greetings { greetings }'),
},
};
createServer(
{
execute,
subscribe,
onSubscribe: (_ctx, msg) => {
const query = queriesStore[msg.payload.query];
if (!query) {
throw new Error('404: Query Not Found');
}
return {
...query,
variableValues: msg.payload.variables,
};
},
},
{
server,
path: '/graphql',
},
);
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'wss://persisted.graphql/queries',
});
(async () => {
const onNext = () => {
};
await new Promise((resolve, reject) => {
client.subscribe(
{
query: 'iWantTheGreetings',
},
{
next: onNext,
error: reject,
complete: resolve,
},
);
});
expect(onNext).toBeCalledTimes(5);
})();
Check the docs folder out for TypeDoc generated documentation.
Read about the exact transport intricacies used by the library in the GraphQL over WebSocket Protocol document.
File a bug, contribute with code, or improve documentation? Read up on our guidelines for contributing and drive development with yarn test --watch
away!