UNPKG

9.23 kBMarkdownView Raw
1[![npm version](https://badge.fury.io/js/graphql-subscriptions.svg)](https://badge.fury.io/js/graphql-subscriptions) [![GitHub license](https://img.shields.io/github/license/apollostack/graphql-subscriptions.svg)](https://github.com/apollographql/graphql-subscriptions/blob/master/LICENSE)
2
3# graphql-subscriptions
4
5GraphQL subscriptions is a simple npm package that lets you wire up GraphQL with a pubsub system (like Redis) to implement subscriptions in GraphQL.
6
7You can use it with any GraphQL client and server (not only Apollo).
8
9### Installation
10
11`npm install graphql-subscriptions graphql` or `yarn add graphql-subscriptions graphql`
12
13> This package should be used with a network transport, for example [subscriptions-transport-ws](https://github.com/apollographql/subscriptions-transport-ws).
14
15### TypeScript
16
17If you are developing a project that uses this module with TypeScript:
18
19* ensure that your `tsconfig.json` `lib` definition includes `"esnext.asynciterable"`
20* `npm install @types/graphql` or `yarn add @types/graphql`
21
22### Getting started with your first subscription
23
24To begin with GraphQL subscriptions, start by defining a GraphQL `Subscription` type in your schema:
25
26```graphql
27type Subscription {
28 somethingChanged: Result
29}
30
31type Result {
32 id: String
33}
34```
35
36Next, add the `Subscription` type to your `schema` definition:
37
38```graphql
39schema {
40 query: Query
41 mutation: Mutation
42 subscription: Subscription
43}
44```
45
46Now, let's create a simple `PubSub` instance - it is a simple pubsub implementation, based on `EventEmitter`. Alternative `EventEmitter` implementations can be passed by an options object
47to the `PubSub` constructor.
48
49```js
50import { PubSub } from 'graphql-subscriptions';
51
52export const pubsub = new PubSub();
53```
54
55Now, implement your Subscriptions type resolver, using the `pubsub.asyncIterator` to map the event you need:
56
57```js
58const SOMETHING_CHANGED_TOPIC = 'something_changed';
59
60export const resolvers = {
61 Subscription: {
62 somethingChanged: {
63 subscribe: () => pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC),
64 },
65 },
66}
67```
68
69> Subscriptions resolvers are not a function, but an object with `subscribe` method, that returns `AsyncIterable`.
70
71Now, the GraphQL engine knows that `somethingChanged` is a subscription, and every time we use `pubsub.publish` over this topic - it will publish it using the transport we use:
72
73```js
74pubsub.publish(SOMETHING_CHANGED_TOPIC, { somethingChanged: { id: "123" }});
75```
76
77> Note that the default PubSub implementation is intended for demo purposes. It only works if you have a single instance of your server and doesn't scale beyond a couple of connections.
78> For production usage you'll want to use one of the [PubSub implementations](#pubsub-implementations) backed by an external store. (e.g. Redis)
79
80### Filters
81
82When publishing data to subscribers, we need to make sure that each subscriber gets only the data it needs.
83
84To do so, we can use `withFilter` helper from this package, which wraps `AsyncIterator` with a filter function, and lets you control each publication for each user.
85
86`withFilter` API:
87- `asyncIteratorFn: (rootValue, args, context, info) => AsyncIterator<any>` : A function that returns `AsyncIterator` you got from your `pubsub.asyncIterator`.
88- `filterFn: (payload, variables, context, info) => boolean | Promise<boolean>` - A filter function, executed with the payload (the published value), variables, context and operation info, must return `boolean` or `Promise<boolean>` indicating if the payload should pass to the subscriber.
89
90For example, if `somethingChanged` would also accept a variable with the ID that is relevant, we can use the following code to filter according to it:
91
92```js
93import { withFilter } from 'graphql-subscriptions';
94
95const SOMETHING_CHANGED_TOPIC = 'something_changed';
96
97export const resolvers = {
98 Subscription: {
99 somethingChanged: {
100 subscribe: withFilter(() => pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC), (payload, variables) => {
101 return payload.somethingChanged.id === variables.relevantId;
102 }),
103 },
104 },
105}
106```
107
108> Note that when using `withFilter`, you don't need to wrap your return value with a function.
109
110### Channels Mapping
111
112You can map multiple channels into the same subscription, for example when there are multiple events that trigger the same subscription in the GraphQL engine.
113
114```js
115const SOMETHING_UPDATED = 'something_updated';
116const SOMETHING_CREATED = 'something_created';
117const SOMETHING_REMOVED = 'something_removed';
118
119export const resolvers = {
120 Subscription: {
121 somethingChanged: {
122 subscribe: () => pubsub.asyncIterator([ SOMETHING_UPDATED, SOMETHING_CREATED, SOMETHING_REMOVED ]),
123 },
124 },
125}
126````
127
128### Payload Manipulation
129
130You can also manipulate the published payload, by adding `resolve` methods to your subscription:
131
132```js
133const SOMETHING_UPDATED = 'something_updated';
134
135export const resolvers = {
136 Subscription: {
137 somethingChanged: {
138 resolve: (payload, args, context, info) => {
139 // Manipulate and return the new value
140 return payload.somethingChanged;
141 },
142 subscribe: () => pubsub.asyncIterator(SOMETHING_UPDATED),
143 },
144 },
145}
146````
147
148### Usage with callback listeners
149
150Your database might have callback-based listeners for changes, for example something like this:
151
152```JS
153const listenToNewMessages = (callback) => {
154 return db.table('messages').listen(newMessage => callback(newMessage));
155}
156
157// Kick off the listener
158listenToNewMessages(message => {
159 console.log(message);
160})
161```
162
163The `callback` function would be called every time a new message is saved in the database. Unfortunately, that doesn't play very well with async iterators out of the box because callbacks are push-based, where async iterators are pull-based.
164
165We recommend using the [`callback-to-async-iterator`](https://github.com/withspectrum/callback-to-async-iterator) module to convert your callback-based listener into an async iterator:
166
167```js
168import asyncify from 'callback-to-async-iterator';
169
170export const resolvers = {
171 Subscription: {
172 somethingChanged: {
173 subscribe: () => asyncify(listenToNewMessages),
174 },
175 },
176}
177````
178
179### Custom `AsyncIterator` Wrappers
180
181The value you should return from your `subscribe` resolver must be an `AsyncIterator`.
182
183You can use this value and wrap it with another `AsyncIterator` to implement custom logic over your subscriptions.
184
185For example, the following implementation manipulate the payload by adding some static fields:
186
187```typescript
188import { $$asyncIterator } from 'iterall';
189
190export const withStaticFields = (asyncIterator: AsyncIterator<any>, staticFields: Object): Function => {
191 return (rootValue: any, args: any, context: any, info: any): AsyncIterator<any> => {
192
193 return {
194 next() {
195 return asyncIterator.next().then(({ value, done }) => {
196 return {
197 value: {
198 ...value,
199 ...staticFields,
200 },
201 done,
202 };
203 });
204 },
205 return() {
206 return Promise.resolve({ value: undefined, done: true });
207 },
208 throw(error) {
209 return Promise.reject(error);
210 },
211 [$$asyncIterator]() {
212 return this;
213 },
214 };
215 };
216};
217```
218
219> You can also take a look at `withFilter` for inspiration.
220
221For more information about `AsyncIterator`:
222- [TC39 Proposal](https://github.com/tc39/proposal-async-iteration)
223- [iterall](https://github.com/leebyron/iterall)
224- [IxJS](https://github.com/ReactiveX/IxJS)
225
226### PubSub Implementations
227
228It can be easily replaced with some other implementations of [PubSubEngine abstract class](https://github.com/apollographql/graphql-subscriptions/blob/master/src/pubsub-engine.ts). Here are a few of them:
229- Use Redis with https://github.com/davidyaha/graphql-redis-subscriptions
230- Use Google PubSub with https://github.com/axelspringer/graphql-google-pubsub
231- Use MQTT enabled broker with https://github.com/davidyaha/graphql-mqtt-subscriptions
232- Use RabbitMQ with https://github.com/cdmbase/graphql-rabbitmq-subscriptions
233- Use AMQP (RabbitMQ) with https://github.com/Surnet/graphql-amqp-subscriptions
234- Use Kafka with https://github.com/ancashoria/graphql-kafka-subscriptions
235- Use Postgres with https://github.com/GraphQLCollege/graphql-postgres-subscriptions
236- Use NATS with https://github.com/moonwalker/graphql-nats-subscriptions
237- Use multiple backends with https://github.com/jcoreio/graphql-multiplex-subscriptions
238- [Add your implementation...](https://github.com/apollographql/graphql-subscriptions/pull/new/master)
239
240You can also implement a `PubSub` of your own, by using the exported abstract class `PubSubEngine` from this package. By using `extends PubSubEngine` you use the default `asyncIterator` method implementation; by using `implements PubSubEngine` you must implement your own `AsyncIterator`.
241
242#### SubscriptionManager **@deprecated**
243
244`SubscriptionManager` is the previous alternative for using `graphql-js` subscriptions directly, and it's now deprecated.
245
246If you are looking for its API docs, refer to [a previous commit of the repository](https://github.com/apollographql/graphql-subscriptions/blob/5eaee92cd50060b3f3637f00c53960f51a07d0b2/README.md)