Apollo GraphQL support

AnyCable can act as a translator between Apollo GraphQL and Action Cable protocols (used by GraphQL Ruby).

That allows us to use the variety of tools compatible with Apollo: client-side libraries, IDEs (such as Apollo Studio).

See also the demo of using AnyCable with Apollo GraphQL React Native application.

Usage

Run anycable-go with graphql_path option specified as follows:

$ anycable-go -graphql_path=/graphql

 ...
 INFO 2021-05-11T11:53:01.186Z context=main Handle GraphQL WebSocket connections at http://localhost:8080/graphql
 ...

# or using env var
$ ANYCABLE_GRAPHQL_PATH=/graphql anycable-go

Now your Apollo-compatible* GraphQL clients can connect to the /graphql endpoint to consume your GraphQL API.

* Currently, there are two protocol implementations supported by AnyCable: graphql-ws and (legacy) subscriptions-transport-ws.

GraphQL Ruby code stays unchanged (make sure you use graphql-anycable plugin).

Other configuration options:

--graphql_channel (ANYCABLE_GRAPHQL_CHANNEL)

GraphQL Ruby channel class name (default: "GraphqlChannel").

--graphql_action (ANYCABLE_GRAPHQL_ACTION)

GraphQL Ruby channel action name (default: "execute").

Client configuration

We test our implementation against the official Apollo WebSocket link configuration described here: Get real-time updates from your GraphQL server.

Authentication

Apollo GraphQL supports passing additional connection params during the connection establishment. For example:

import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';

const wsLink = new GraphQLWsLink(createClient({
  url: 'ws://localhost:8080/graphql',
  connectionParams: {
    token: 'some-token',
  },
}));

AnyCable passes these params via the x-apollo-connection HTTP header, which you can access in your ApplicationCable::Connection#connect method:

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :user

    def connect
      user = find_user
      reject_unauthorized_connection unless user

      self.user = user
    end

    private

    def find_user
      header = request.headers["x-apollo-connection"]
      return unless header

      # Header contains JSON-encoded params
      payload = JSON.parse(header)

      User.find_by_token(payload["token"])
    end
  end
end

Note that the header contains JSON-encoded connection params object.

Using with JWT identification

You can use JWT identification along with Apollo integration by specifying the token either via query params (e.g., ws://localhost:8080/graphql?jid=<token>) or by passing it along with connection params like this:

const wsLink = new GraphQLWsLink(createClient({
  url: 'ws://localhost:8080/graphql',
  connectionParams: {
    jid: '<token>',
  },
}));