DEV Community

Dmitry Amelchenko
Dmitry Amelchenko

Posted on

Getting Apollo client to work with @aws_subscribe

I'm building my killer mobile app on graphql.

On the backend I'm using AWS AppSync -- I'm still not bought into the magic of Amplify -- there is just a way too much magic going on behind the scenes, which might work for simple HelloWorld projects, but, if I'm going to scale my app eventually (and I am going to), I'd better really understand what's going on.

Apollo is the de-factor standard for GraphQL client. Unfortunately, Apollo is progressing forward little faster than AWS AppSync. As such, Apollo's latest version does not support headers passing for web sockets connection (like it used to in the earlier versions). This means that getting it to work with AppSync subscriptions is going to be a challenge.

I spent fair amount of time reading all the blogs and stack overflow questions for the past 2 years -- non of them worked for me. The simplest solution that I was able to come up with -- use the latests Apollo client for "queries" and "mutations", but fall back for the AWS client implementation for "subscriptions".

Here is the JS file that defines a connection which can be used for "queries" and "mutations":

import { setContext } from '@apollo/client/link/context'

import {
  ApolloClient,
  InMemoryCache,
  HttpLink,
} from "@apollo/client"

import { API_URI, API_KEY } from "@env"

const authLink = setContext((_, { headers }) => {
  return {
    headers: {
      ...headers,
      'X-Api-Key': API_KEY,
    },
  }
})
const httpLink = new HttpLink({
  uri: API_URI,
})

export const gqlClient = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
})
Enter fullscreen mode Exit fullscreen mode

I do define a different type of the gql connection to be used with "subscriptions" in the following js file (note, that the end point URL is exactly the same as for the other connection, which is different in most of the docs I was reading):

import { createAuthLink } from "aws-appsync-auth-link"
import { createSubscriptionHandshakeLink } from "aws-appsync-subscription-link"

import { ApolloLink } from "apollo-link"
import { createHttpLink } from "apollo-link-http"
import ApolloClient from "apollo-client"
import { InMemoryCache } from "apollo-cache-inmemory"

import {
  API_URI, API_KEY, REGION,
} from "@env"

const url = API_URI
const region = REGION
const auth = {
  type: 'API_KEY',
  apiKey: API_KEY,
}

const httpLink = createHttpLink({ uri: url })

const link = ApolloLink.from([
  createAuthLink({ url, region, auth }),
  createSubscriptionHandshakeLink({ url, region, auth }, httpLink),
])

const subscriptionClient = new ApolloClient({
  link,
  cache: new InMemoryCache(),
})

export default subscriptionClient
Enter fullscreen mode Exit fullscreen mode

Here is how I call gql query:

import * as CONST from '../../consts.js'

const messagesList = (await CONST.gqlClient
        .query({
          query: gql`
        query getMessagesList($chatUuid: String!, $lastLoaded: AWSDateTime!) {
          getMessagesList(chatUuid: $chatUuid, lastLoaded: $lastLoaded){
            uuid,
            messageUuid, 
            text, 
            createdAt,
            updatedAt
          }
        }`,
          variables: {
            chatUuid,
            lastLoaded,
          },
          // fetchPolicy: "network-only",
        })).data.getMessagesList

Enter fullscreen mode Exit fullscreen mode

And the subscription is called like this:

import subscriptionClient from '../../subscriptionClient'

const subscription = subscriptionClient
      .subscribe({
        query: gql`
        subscription onSendMessage($chatUuid: String!) {
          onSendMessage (chatUuid: $chatUuid) {
            chatUuid
            createdAt
            messageUuid
            text
            updatedAt
            uuid          }
        }
        `,
        variables: {
          chatUuid,
        },
      })
      .subscribe({
        next(data) {
          console.log({ data })
        },
        error({ error }) {
          console.log({ error })
        },
        complete() { console.log("subs. DONE") }, // never printed
      })

Enter fullscreen mode Exit fullscreen mode

Unfortunately, I have to import 2 sets of Apollo npm's -- one from Apollo and the other one from AWS AppSynch. Hopefully this will not cause conflict in the nearest future, and hopefully AWS will fgure how how to make AppSync to work not only with Amplify, but also with more standard solutions like Apollo client.

To come up with this simple solution I had to sift through the following list of blog posts:

https://github.com/awslabs/aws-mobile-appsync-sdk-js#using-authorization-and-subscription-links-with-apollo-client-v3-no-offline-support

https://www.tutorialguruji.com/react-js/appsync-subscriptions-with-apolloclient-in-react

https://dev.to/sayanide/beginner-s-guide-to-using-websockets-in-apollo-client-graphql-subscription-11m

https://github.com/apollographql/apollo-feature-requests/issues/224

I posted the original questions here: https://stackoverflow.com/questions/70326194/authenticating-apollo-client-for-appsync-to-work-with-aws-subscribe

and glad that I was able to provide a solution for it.

If you are interested, here are the links to my mobile killer app:

https://github.com/echowaves/WiSaw

https://github.com/echowaves/WiSaw.cdk

https://www.wisaw.com/

And this was originally posted here: https://www.echowaves.com/post/getting-apollo-client-to-work-with-aws_subscribe

Oldest comments (1)

Collapse
 
joanfaris profile image
JoanFaris

When I subscribe to this subscription in the aws console -- it does trigger just fine. I'm just never able to get it to work in the client app. How many days vashikaran works