Skip to content
This repository has been archived by the owner on Sep 15, 2020. It is now read-only.
/ poc-aws-appsync Public archive

PoC playing with AWS AppSync and GraphQL subscriptions (websockets, mqtt, etc)

Notifications You must be signed in to change notification settings

0xdevalias/poc-aws-appsync

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PoC-AWS-AppSync

PoC playing with AWS AppSync and GraphQL subscriptions (websockets, mqtt, etc) in React.

GraphQL Schema, ..TODO:etc..

TODO: how to set up in AWS Console

References:

React

First step, we need to create a new React application. This is super easy..

mkdir poc-aws-appsync && cd poc-aws-appsync
npm init react-app .

Amplify (the manual way)

Next, we're going to make use of AmplifyJS and it's React components to simplify wiring AppSync into our frontend.

You might want to look at the AppSync React starter project as a reference (and for better project layout/code style than this PoC).

First we want to install some dependencies:

  • @aws-amplify/core: Pull all the modularised features together
  • @aws-amplify/api: GraphQL API support
  • @aws-amplify/pubsub: GraphQL subscription support
  • aws-amplify-react : React components
npm install @aws-amplify/core @aws-amplify/api @aws-amplify/pubsub aws-amplify-react

Next we need to wire them into our React app. Something that wasn't immediately obvious to me was how to use the modularised dependencies. It seems that we can just import core, and then import the other modules we want, and it appears to magically wire itself together. For example:

Bad:

import Amplify from '@aws-amplify/core';
//import API, {graphqlOperation} from '@aws-amplify/api'

console.log("Automagicly Wired?", Amplify.API !== null); // false

Good:

import Amplify from '@aws-amplify/core';
import API, {graphqlOperation} from '@aws-amplify/api'

console.log("Automagicly Wired?", Amplify.API !== null); // true

Even though it seems to automagically work, we get 'unused var' errors for the other imports.. so it seems we can be a little more explicit to resolve this:

import Amplify from '@aws-amplify/core';
import API, {graphqlOperation} from '@aws-amplify/api'

Amplify.register(API);

console.log("Automagicly Wired?", Amplify.API !== null); // true

For the next part you will need to grab a few settings from the aptly named 'Settings' page in your project within the AWS AppSync Console. You could use the 'Download Config' button, or copy the relevant bits manually.

Alternatively, you could grab them with the AWS AppSync CLI:

# List all API's
aws appsync list-graphql-apis

# Get specific API
aws appsync get-graphql-api --api-id ABC123MYAPIID

# Lookup API keys for API
aws appsync list-api-keys --api-id ABC123MYAPIID

Settings:

  • aws_appsync_graphqlEndpoint: The value in uris.GRAPHQL
  • aws_appsync_region: You can get this from uris.GRAPHQL, the part just before .amazonaws.com. Eg. ap-southeast-2
  • aws_appsync_authenticationType: The value in authenticationType
  • aws_appsync_apiKey: The value in apiKeys[].id

Since we're using API_KEY auth for our API here, we need to configure our app appropriately.:

index.js:

//..snip..

import Amplify from '@aws-amplify/core';
import API, {graphqlOperation} from '@aws-amplify/api'
import PubSub from '@aws-amplify/pubsub';

Amplify.register(API);
Amplify.register(PubSub);

const aws_config = {
  'aws_appsync_graphqlEndpoint': 'https://xxxxxx.appsync-api.us-east-1.amazonaws.com/graphql',
  'aws_appsync_region': 'us-east-1',
  'aws_appsync_authenticationType': 'API_KEY',
  'aws_appsync_apiKey': 'da2-xxxxxxxxxxxxxxxxxxxxxxxxxx',
};

Amplify.API.configure(aws_config);

//..snip..

Now that we've wired in our authentication, we want to test our subscriptions. We can hack this in and output anything received to the console by adding the following code just after our Amplify.configure(aws_config) line. We'll also manually hack in a mutation that we should hopefully see on the subscription:

index.js:

//..snip..

Amplify.configure(aws_config);

// Configure our mutation
const SendCommand = `mutation SendCommand($channelID: ID!, $command: String!) {
  sendCommand(channelID: $channelID, command: $command) {
    channelID
    command
    sentAt
  }
}`;

// Configure our subscription
const SubscribeToChannelCommands = `subscription SubscribeToChannelCommands($channelID: ID!) {
  receivedCommand(channelID: $channelID) {
    channelID
    command
    sentAt
  }
}`;

// Subscribe to channel
const subscription = Amplify.API.graphql(
  graphqlOperation(SubscribeToChannelCommands, { channelID: 'abc123' })
).subscribe({
  next: (eventData) => console.log("Subscription:", eventData, eventData.value.data),
  error: (eventData) => console.log("Subscription error:", eventData)
});

// Send mutation after a short delay
setTimeout(function(){
  const sendCommand = Amplify.API.graphql(
    graphqlOperation(SendCommand, { channelID: 'abc123', command: "FOOCOMMAND"})
  ).then(
    (result) => console.log("Mutation:", result),
    (error) => console.log("Mutation error:", error)
  );
}, 1000);

//..snip..

We can then start our app (npm start) and check for the console output, which should look something like the following (when not expanded):

Mutation: {data: {…}}
Subscription: {provider: AWSAppSyncProvider, value: {…}} {receivedCommand: {…}}

Now a potential issue you may run into with your own subscriptions is the strange requirements around the shape of both the mutation, and the subscription when using @aws_subscribe. As alluded to in this post, if we have a parameter that is not included in the selection set of our mutation, it will just silently not fire.

For example, in my testing, this didn't work:

const SendCommand = `mutation sendCommand($channelID: ID!, $command: String!) {
  sendCommand(channelID: $channelID, command: $command) {
    sentAt
  }
}`;

const SubscribeToChannelCommands = `subscription SubscribeToChannelCommands($channelID: ID!) {
  receivedCommand(channelID: $channelID) {
    command
    sentAt
  }
}`;

Whereas when I changed the shape of the mutation to the following, I started seeing my subscription being fired:

const SendCommand = `mutation sendCommand($channelID: ID!, $command: String!) {
  sendCommand(channelID: $channelID, command: $command) {
    channelID
    command
    sentAt
  }
}`;

const SubscribeToChannelCommands = `subscription SubscribeToChannelCommands($channelID: ID!) {
  receivedCommand(channelID: $channelID) {
    channelID
    command
    sentAt
  }
}`;

While it is alluded to in the documentation (rather unclearly IMO), it doesn't really make sense to me why the selection set inside the subscription appears to be entirely ignored, and the selection set in the mutation is what actually matters/is returned..

Subscriptions are triggered from mutations and the mutation selection set is sent to subscribers. ..snip.. Although the subscription query above is needed for client connections and tooling, the selection set that is received by subscribers is specified by the client triggering the mutation. ..snip.. The return type of a subscription field in your schema must match the return type of the corresponding mutation field.

Perhaps it's because we can subscribe to multiple mutations, and if we were controlling the selection set on the subscription side.. we wouldn't be able to define the shape?

In any case.. we should probably ensure both of these match so that other clients/libraries/frameworks that look at the subscription selection set don't get themselves confused.

Now that you have a basic hacked together test bed, you might want to wire things into react 'properly':

Amplify (the easy way, CLI)

Looking at our AWS AppSync Console, the root page for our project actually gives us some easy commands to inject support into our application (if we're using the Amplify CLI)

TODO: Explore this more

amplify init

amplify add codegen --apiId ABC123YOURAPPSYNCAPIID

Infrastructure as Code (CloudFormation, CDK, etc)

Playing with web UI's is nice for learning, but at the end of the day we want everything tracked and comitted in git, defined and re-deployable.

CloudFormation

AWS Cloud Development Kit (CDK)

AWS Serverless Application Model (SAM)