⚡️ Pro Feature ⚡️ This feature is bundled with GraphQL-Pro.
After creating an app on Ably, you can hook it up to your GraphQL schema.
This subscription implementation uses a hybrid approach:
So, the lifecycle goes like this:
subscriptionquery is sent by HTTP Post to your server (just like a
Here’s another look:
1. Subscription is created in your app HTTP POST .----------> write to Redis 📱 ⚙️ -----> 💾 <---------' X-Subscription-ID: 1234 2. Client opens a connection to Ably websocket 📱 <---------> ☁️ 3. The app sends updates via Ably ⚙️ ---------> ☁️ ------> 📱 POST update (via gem) (via websocket) 4. When the client unsubscribes, Ably notifies the app webhook ⚙️ <-------- ☁️ (disconnect) 📱
By using this configuration, you can use GraphQL subscriptions without hosting a push server yourself!
ably-rest to your
Subscriptions require a persistent Redis database, configured with:
maxmemory-policy noeviction # optional, more durable persistence: appendonly yes
Otherwise, Redis will drop data that doesn’t fit in memory (read more in “Redis persistence”).
If you’re already using Redis in your application, see “Storing Data in Redis” for options to isolate data and tune your configuration.
redis to your
bundle install. Then create a Redis instance:
# for example, in an initializer: $graphql_subscriptions_redis = Redis.new # default connection
Then, that Redis client is passed to the Subscription configuration:
class MySchema < GraphQL::Schema use GraphQL::Pro::AblySubscriptions, redis: $graphql_subscriptions_redis, ably: Ably::Rest.new(key: ABLY_API_KEY) end
That connection will be used for managing subscription state. All writes to Redis are prefixed with
During execution, GraphQL will assign a
subscription_id to the
context hash. The client will use that ID to listen for updates, so you must return the
subscription_id in the response headers.
result.context[:subscription_id] as the
X-Subscription-ID header. For example:
result = MySchema.execute(...) # For subscriptions, return the subscription_id as a header if result.subscription? response.headers["X-Subscription-ID"] = result.context[:subscription_id] end render json: result
This way, the client can use that ID as a Ably channel.
For CORS requests, you need a special header so that clients can read the custom header:
if result.subscription? response.headers["X-Subscription-ID"] = result.context[:subscription_id] # Required for CORS requests: response.headers["Access-Control-Expose-Headers"] = "X-Subscription-ID" end
Read more here: “Using CORS”.
Your server needs to receive webhooks from Ably when clients disconnect. This keeps your local subscription database in sync with Ably.
Note: if you’re setting up in a development environment you should follow the Developing with webhooks section first
Mount the Rack app for handling webhooks from Ably. For example, on Rails:
# config/routes.rb # Include GraphQL::Pro's routing extensions: using GraphQL::Pro::Routes Rails.application.routes.draw do # ... # Handle webhooks for subscriptions: mount MySchema.ably_webhooks_client, at: "/ably_webhooks" end
Since subscription state is stored in the database, then reloaded for pushing updates, you have to serialize and reload your query
By default, this is done with
load methods, but you can provide custom implementations as well. To customize the serialization logic, create a subclass of
GraphQL::Pro::Subscriptions and override
class CustomSubscriptions < GraphQL::Pro::AblySubscriptions def dump_context(ctx) context_hash = ctx.to_h # somehow convert this hash to a string, return the string end def load_context(ctx_string) # Given the string from the DB, create a new hash # to use as `context:` end end
Then, use your custom subscriptions class instead of the built-in one for your schema:
class MySchema < GraphQL::Schema # Use custom subscriptions instead of GraphQL::Pro::AblySubscriptions # to get custom serialization logic use CustomSubscriptions, ... end
That gives you fine-grained control of context reloading.
You can monitor subscription state in the GraphQL-Pro Dashboard:
<a href="/subscriptions/redis_dashboard_1.png" target="_blank" class="img-link"> <img src="/subscriptions/redis_dashboard_1.png" title="Redis Subscription Dashboard" alt="Redis Subscription Dashboard" /> </a> <a href="/subscriptions/redis_dashboard_2.png" target="_blank" class="img-link"> <img src="/subscriptions/redis_dashboard_2.png" title="Redis Subscription Detail" alt="Redis Subscription Detail" /> </a>
At any time, you can reset your subscription database with the “Reset” button in the GraphQL-Pro Dashboard, or in Ruby:
# Wipe all subscription data from the DB: MySchema.subscriptions.clear
To receive webhooks in development, you can use ngrok. It gives you a public URL which you can setup with Ably, then any hooks delivered to that URL will be forwarded to your development environment.