The Amazon AppSync Events API was recently announced and is a new feature to use a WebSocket API for real-time communication. Based on the Amazon Cognito User Pool with Managed Login and guide for GraphQL Data API in Amazon AppSync, this guide shows you how to add real-time communication to your React application using WebSockets and the Amazon AppSync Events API with Amazon Cognito.
Amazon AppSync - and GraphQL in general - do support real-time communication; it’s called GraphQL Subscriptions are usually tricky to configure. GraphQL is great for fetching data; and Amazon AppSync is great for building a serverless GraphQL API. There is no need for a GraphQL subscriptions layers, there is just the need for a real-time communication layer!
Here comes Amazon AppSync Events API with WebSockets and some new APIs to publish messages to the clients and for clients, to subscribe to channels.
Amazon AppSync Events
Within Amazon AppSync Events, namespace
and channel
are the two main concepts. A namespace
is a container for channels. A channel
is a container for messages. Clients can subscribe to a channel (public/*
or private/user-id
e.g.) and receive messages. You can publish messages to a channel using a HTTP request and clients subscribe to a channel using a WebSocket connection.
For authentication, Amazon AppSync Events supports the same authentication methods as the GraphQL Data API: Amazon Cognito, AWS IAM, API Key, OpenID Connect, and AWS Lambda.
Amazon AppSync Events API with AWS CDK
In addition to the maybe existing GraphQL Data API, Amazon AppSync now supports a new kind of API: the GraphQL Events API. Therefore, you need to create a new AppSync API in your CDK CloudFormation Stack:
const events = new aws_appsync.CfnApi(stack, "Events", {
name: `serverless-events`,
eventConfig: {
authProviders: [
{
authType: AuthorizationType.USER_POOL,
cognitoConfig: {
awsRegion: Stack.of(stack).region,
userPoolId: users.userPoolId,
},
},
{ authType: AuthorizationType.IAM },
],
connectionAuthModes: [{ authType: AuthorizationType.USER_POOL }],
defaultPublishAuthModes: [{ authType: AuthorizationType.USER_POOL }],
defaultSubscribeAuthModes: [{ authType: AuthorizationType.USER_POOL }],
},
});
This will create a new AppSync Events API and enable two authentication methods:
- CognitoUser Pool Authentication, and
- AWS IAM Authentication.
Clients using the WebSocket connection will use Cognito User Pools and other services will use IAM authentication to publish messages to the AppSync Events API. Individual authentication modes can be configured per namespace
and the default authentication is set to Cognito User Pools here.
Configure AppSync Namespaces
This creates two new namespace
configurations: public
and private
. For the public
namespace, clients can connect to all channels. For the private
namespace, clients can only connect to a private channel with their user ID. AWS AppSync Events supports custom handlers for onSubscribe
and onPublish
for every namespace.
new CfnChannelNamespace(stack, "ChannelNamespacePublic", {
name: `public`,
apiId: events.attrApiId,
publishAuthModes: [{ authType: AuthorizationType.IAM }],
subscribeAuthModes: [{ authType: AuthorizationType.USER_POOL }],
});
For the public
channel, restricting the publishAuthModes
to IAM authentication is sufficient; for the private
channel, we need to implement a custom handler for the onSubscribe
event:
new CfnChannelNamespace(stack, "ChannelNamespacePrivate", {
name: `private`,
apiId: events.attrApiId,
codeHandlers: `import { util } from '@aws-appsync/utils'
export function onSubscribe(ctx) {
if (ctx.info.channel.path !== '/private/' + ctx.identity.sub) {
console.log(ctx.identity.sub + 'tried connecting to wrong channel: ' + ctx.channel)
util.unauthorized()
}
}`,
publishAuthModes: [{ authType: AuthorizationType.IAM }],
subscribeAuthModes: [{ authType: AuthorizationType.USER_POOL }],
});
Run cdk deploy
to create the AppSync Events API and the two namespaces.
You can also see the two namespaces in the AWS Management Console:
Next, we need to configure the React application to establish a WebSocket connection to the AppSync Events API.
Configure React & WebSocket for AppSync Events
Based on the Cognito User Pool with Managed Login and guide for GraphQL Data API in AWS AppSync, there is a working React application that uses the Apollo Client to fetch data from the AppSync GraphQL Data API; and most importantly, this shows how to use useAuth()
hook to get the Cognito Access Token.
For the WebSocket connection, there exists an official AppSync Events API Client; but a normal WebSocket connection is also compatible with the AppSync. So let’s use the raw WebSocket connection for this:
export default function App() {
const auth = useAuth();
const authData = JSON.stringify({
host: `your-id.appsync-api.eu-central-1.amazonaws.com`,
Authorization: auth.user!.access_token,
});
socket = new WebSocket(
`wss://your-api.appsync-realtime-api.eu-central-1.amazonaws.com/event/realtime`,
[
"aws-appsync-event-ws",
`header-${btoa(authData)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "")}`,
]
);
socket.onopen = () => {
socket!.send(JSON.stringify({ type: "connection_init" }));
};
}
The WebSocket endpoint is available as the realtime
endpoint of your AppSync Events API; the connection is authenticated using the Cognito Access Token and passed to the WebSocket connection as additional headers.
As soon as the connection is established, you can subscribe to a channel and receive messages. Wait for the connection_ack
message and then you can send subscribe
message types to the WebSocket connection.
const subscribe = (channel: string) => {
socket.send(
JSON.stringify({
type: "subscribe",
id: crypto.randomUUID(),
channel,
authorization: {
host: `your-id.appsync-api.eu-central-1.amazonaws.com`,
Authorization: auth.user!.access_token,
},
})
);
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log(data);
if (data.type === "connection_ack") {
subscribe("public/*");
subscribe(`private/${auth.user!.profile.sub}`);
}
};
This is the basic connection and subscription handling for the AppSync Events API using basic WebSocket connection without the Amplify library. Most importantly, this uses the same Cognito session as the AppSync GraphQL Data API; both using the same react-oidc-context
library with additional complexity or overhead.
Publish Messages with Events Rest API
As the authentication method for the AppSync Events API is Cognito User Pools for all subscriptions and AWS_IAM for publishing messages, AWS AppSync can publish messages using the AWS Management Console.
Provide at least one message and select the public
or private
namespace; you should be able to publish messages to the AppSync Events API and retrieve them using the WebSocket connection in your React application. Awesome! 🎉