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 |
|
5 | GraphQL subscriptions is a simple npm package that lets you wire up GraphQL with a pubsub system (like Redis) to implement subscriptions in GraphQL.
|
6 |
|
7 | You 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 |
|
17 | If 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 |
|
24 | To begin with GraphQL subscriptions, start by defining a GraphQL `Subscription` type in your schema:
|
25 |
|
26 | ```graphql
|
27 | type Subscription {
|
28 | somethingChanged: Result
|
29 | }
|
30 |
|
31 | type Result {
|
32 | id: String
|
33 | }
|
34 | ```
|
35 |
|
36 | Next, add the `Subscription` type to your `schema` definition:
|
37 |
|
38 | ```graphql
|
39 | schema {
|
40 | query: Query
|
41 | mutation: Mutation
|
42 | subscription: Subscription
|
43 | }
|
44 | ```
|
45 |
|
46 | Now, 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
|
47 | to the `PubSub` constructor.
|
48 |
|
49 | ```js
|
50 | import { PubSub } from 'graphql-subscriptions';
|
51 |
|
52 | export const pubsub = new PubSub();
|
53 | ```
|
54 |
|
55 | Now, implement your Subscriptions type resolver, using the `pubsub.asyncIterator` to map the event you need:
|
56 |
|
57 | ```js
|
58 | const SOMETHING_CHANGED_TOPIC = 'something_changed';
|
59 |
|
60 | export 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 |
|
71 | Now, 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
|
74 | pubsub.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 |
|
82 | When publishing data to subscribers, we need to make sure that each subscriber gets only the data it needs.
|
83 |
|
84 | To 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 |
|
90 | For 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
|
93 | import { withFilter } from 'graphql-subscriptions';
|
94 |
|
95 | const SOMETHING_CHANGED_TOPIC = 'something_changed';
|
96 |
|
97 | export 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 |
|
112 | You 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
|
115 | const SOMETHING_UPDATED = 'something_updated';
|
116 | const SOMETHING_CREATED = 'something_created';
|
117 | const SOMETHING_REMOVED = 'something_removed';
|
118 |
|
119 | export 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 |
|
130 | You can also manipulate the published payload, by adding `resolve` methods to your subscription:
|
131 |
|
132 | ```js
|
133 | const SOMETHING_UPDATED = 'something_updated';
|
134 |
|
135 | export 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 |
|
150 | Your database might have callback-based listeners for changes, for example something like this:
|
151 |
|
152 | ```JS
|
153 | const listenToNewMessages = (callback) => {
|
154 | return db.table('messages').listen(newMessage => callback(newMessage));
|
155 | }
|
156 |
|
157 | // Kick off the listener
|
158 | listenToNewMessages(message => {
|
159 | console.log(message);
|
160 | })
|
161 | ```
|
162 |
|
163 | The `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 |
|
165 | We 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
|
168 | import asyncify from 'callback-to-async-iterator';
|
169 |
|
170 | export const resolvers = {
|
171 | Subscription: {
|
172 | somethingChanged: {
|
173 | subscribe: () => asyncify(listenToNewMessages),
|
174 | },
|
175 | },
|
176 | }
|
177 | ````
|
178 |
|
179 | ### Custom `AsyncIterator` Wrappers
|
180 |
|
181 | The value you should return from your `subscribe` resolver must be an `AsyncIterator`.
|
182 |
|
183 | You can use this value and wrap it with another `AsyncIterator` to implement custom logic over your subscriptions.
|
184 |
|
185 | For example, the following implementation manipulate the payload by adding some static fields:
|
186 |
|
187 | ```typescript
|
188 | import { $$asyncIterator } from 'iterall';
|
189 |
|
190 | export 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 |
|
221 | For 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 |
|
228 | It 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 |
|
240 | You 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 |
|
246 | If 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)
|