Page updated Feb 19, 2024

Preview: AWS Amplify's new code-first DX (Gen 2)

The next generation of Amplify's backend building experience with a TypeScript-first DX.

Get started

Subscribe to real-time events

In this guide, we will outline the benefits of enabling real-time data integrations and how to set up and filter these subscriptions. We will also cover how to unsubscribe from subscriptions.

Before you begin, you will need:

Set up a real-time subscription

Subscriptions is a GraphQL feature that allows the server to send data to its clients when a specific event happens. For example, you can subscribe to an event when a new record is created, updated, or deleted through the API. You can enable real-time data integration in your app with a subscription.

import { generateClient } from 'aws-amplify/api'; import * as subscriptions from './graphql/subscriptions'; const client = generateClient(); // Subscribe to creation of Todo const createSub = client .graphql({ query: subscriptions.onCreateTodo }) .subscribe({ next: ({ data }) => console.log(data), error: (error) => console.warn(error) }); // Subscribe to update of Todo const updateSub = client .graphql({ query: subscriptions.onUpdateTodo }) .subscribe({ next: ({ data }) => console.log(data), error: (error) => console.warn(error) }); // Subscribe to deletion of Todo const deleteSub = client .graphql({ query: subscriptions.onDeleteTodo }) .subscribe({ next: ({ data }) => console.log(data), error: (error) => console.warn(error) }); // Stop receiving data updates from the subscription createSub.unsubscribe(); updateSub.unsubscribe(); deleteSub.unsubscribe();
1import { generateClient } from 'aws-amplify/api';
2import * as subscriptions from './graphql/subscriptions';
3
4const client = generateClient();
5
6// Subscribe to creation of Todo
7const createSub = client
8 .graphql({ query: subscriptions.onCreateTodo })
9 .subscribe({
10 next: ({ data }) => console.log(data),
11 error: (error) => console.warn(error)
12 });
13
14// Subscribe to update of Todo
15const updateSub = client
16 .graphql({ query: subscriptions.onUpdateTodo })
17 .subscribe({
18 next: ({ data }) => console.log(data),
19 error: (error) => console.warn(error)
20 });
21
22// Subscribe to deletion of Todo
23const deleteSub = client
24 .graphql({ query: subscriptions.onDeleteTodo })
25 .subscribe({
26 next: ({ data }) => console.log(data),
27 error: (error) => console.warn(error)
28 });
29
30// Stop receiving data updates from the subscription
31createSub.unsubscribe();
32updateSub.unsubscribe();
33deleteSub.unsubscribe();

Set up server-side subscription filters

Subscriptions take an optional filter argument to define service-side subscription filters:

import { generateClient } from 'aws-amplify/api'; const client = generateClient(); const variables = { filter: { // Only receive Todo messages where the "type" field is "Personal" type: { eq: 'Personal' } } }; const sub = client .graphql({ query: subscriptions.onCreateTodo, variables }) .subscribe({ next: ({ data }) => console.log(data), error: (error) => console.warn(error) });
1import { generateClient } from 'aws-amplify/api';
2
3const client = generateClient();
4
5const variables = {
6 filter: {
7 // Only receive Todo messages where the "type" field is "Personal"
8 type: { eq: 'Personal' }
9 }
10};
11
12const sub = client
13 .graphql({
14 query: subscriptions.onCreateTodo,
15 variables
16 })
17 .subscribe({
18 next: ({ data }) => console.log(data),
19 error: (error) => console.warn(error)
20 });

If you want to get all subscription events, don't specify any filter parameters.

Limitations:

  • Specifying an empty object {} as a filter is not recommended. Using {} as a filter might cause inconsistent behavior based on your data model's authorization rules.
  • If you are using dynamic group authorization and you authorize based on a single group per record, subscriptions are only supported if the user is part of five or fewer user groups.
  • Additionally, if you authorize by using an array of groups (groups: [String]),
    • subscriptions are only supported if the user is part of 20 or fewer groups
    • you can only authorize 20 or fewer user groups per record

Subscription connection status updates

Now that your application is set up and using subscriptions, you may want to know when the subscription is finally established, or reflect to your users when the subscription isn't healthy. You can monitor the connection state for changes through the Hub local eventing system.

import { CONNECTION_STATE_CHANGE, ConnectionState } from 'aws-amplify/api'; import { Hub } from 'aws-amplify/utils'; Hub.listen('api', (data: any) => { const { payload } = data; if (payload.event === CONNECTION_STATE_CHANGE) { const connectionState = payload.data.connectionState as ConnectionState; console.log(connectionState); } });
1import { CONNECTION_STATE_CHANGE, ConnectionState } from 'aws-amplify/api';
2import { Hub } from 'aws-amplify/utils';
3
4Hub.listen('api', (data: any) => {
5 const { payload } = data;
6 if (payload.event === CONNECTION_STATE_CHANGE) {
7 const connectionState = payload.data.connectionState as ConnectionState;
8 console.log(connectionState);
9 }
10});

Subscription connection states

  • Connected - Connected and working with no issues.
  • ConnectedPendingDisconnect - The connection has no active subscriptions and is disconnecting.
  • ConnectedPendingKeepAlive - The connection is open, but has missed expected keep-alive messages.
  • ConnectedPendingNetwork - The connection is open, but the network connection has been disrupted. When the network recovers, the connection will continue serving traffic.
  • Connecting - Attempting to connect.
  • ConnectionDisrupted - The connection is disrupted and the network is available.
  • ConnectionDisruptedPendingNetwork - The connection is disrupted and the network connection is unavailable.
  • Disconnected - Connection has no active subscriptions and is disconnecting.
Troubleshooting
Troubleshooting connection issues and automated reconnection

Connections between your application and backend subscriptions can be interrupted for various reasons, including network outages or the device entering sleep mode. Your subscriptions will automatically reconnect when it becomes possible to do so.

While offline, your application will miss messages and will not automatically catch up when reconnected. Depending on your use case, you may want to take action for your app to catch up when it comes back online.

import { generateClient, CONNECTION_STATE_CHANGE, ConnectionState } from 'aws-amplify/api' import { Hub } from 'aws-amplify/utils' const client = generateClient() const fetchRecentData = () => { // Retrieve some/all data from AppSync const allTodos = await client.graphql({ query: queries.listTodos }); } let priorConnectionState: ConnectionState; Hub.listen("api", (data: any) => { const { payload } = data; if ( payload.event === CONNECTION_STATE_CHANGE ) { if (priorConnectionState === ConnectionState.Connecting && payload.data.connectionState === ConnectionState.Connected) { fetchRecentData(); } priorConnectionState = payload.data.connectionState; } }); const createSub = client.graphql( { query: subscriptions.onCreateTodo } ).subscribe({ next: payload => // Process incoming messages }); const updateSub = client.graphql( { query: subscriptions.onUpdateTodo } ).subscribe({ next: payload => // Process incoming messages }); const deleteSub = client.graphql( { query: subscriptions.onDeleteTodo } ).subscribe({ next: payload => // Process incoming messages }); const cleanupSubscriptions = () => { createSub.unsubscribe(); updateSub.unsubscribe(); deleteSub.unsubscribe(); }
1import { generateClient, CONNECTION_STATE_CHANGE, ConnectionState } from 'aws-amplify/api'
2import { Hub } from 'aws-amplify/utils'
3
4const client = generateClient()
5
6const fetchRecentData = () => {
7 // Retrieve some/all data from AppSync
8 const allTodos = await client.graphql({ query: queries.listTodos });
9}
10
11let priorConnectionState: ConnectionState;
12
13Hub.listen("api", (data: any) => {
14 const { payload } = data;
15 if (
16 payload.event === CONNECTION_STATE_CHANGE
17 ) {
18
19 if (priorConnectionState === ConnectionState.Connecting && payload.data.connectionState === ConnectionState.Connected) {
20 fetchRecentData();
21 }
22 priorConnectionState = payload.data.connectionState;
23 }
24});
25
26const createSub = client.graphql(
27 { query: subscriptions.onCreateTodo }
28).subscribe({
29 next: payload => // Process incoming messages
30});
31
32const updateSub = client.graphql(
33 { query: subscriptions.onUpdateTodo }
34).subscribe({
35 next: payload => // Process incoming messages
36});
37
38const deleteSub = client.graphql(
39 { query: subscriptions.onDeleteTodo }
40).subscribe({
41 next: payload => // Process incoming messages
42});
43
44const cleanupSubscriptions = () => {
45 createSub.unsubscribe();
46 updateSub.unsubscribe();
47 deleteSub.unsubscribe();
48}

Create a custom GraphQL subscription by ID

This brief walkthrough provides additional step-by-step guidance for creating a custom GraphQL subscription that will only be connected and triggered by a mutation containing a specific ID as an argument. Take, for example, the following GraphQL schema:

type Post @model @auth(rules: [{ allow: public }]) { id: ID! title: String! content: String comments: [Comment] @hasMany } type Comment @model @auth(rules: [{ allow: public }]) { id: ID! content: String }
1type Post @model @auth(rules: [{ allow: public }]) {
2 id: ID!
3 title: String!
4 content: String
5 comments: [Comment] @hasMany
6}
7
8type Comment @model @auth(rules: [{ allow: public }]) {
9 id: ID!
10 content: String
11}

By default, subscriptions will be created for the following mutations:

# Post type onCreatePost onUpdatePost onDeletePost # Comment type onCreateComment onUpdateComment onDeleteComment
1# Post type
2onCreatePost
3onUpdatePost
4onDeletePost
5
6# Comment type
7onCreateComment
8onUpdateComment
9onDeleteComment

One operation that is not covered is how to subscribe to comments for only a single, specific post.

Because the schema has a one-to-many relationship enabled between posts and comments, you can use the auto-generated field postCommentsId, which defines the relationship, to set this up in a new Subscription.

To implement this, update the schema with the following:

type Post @model @auth(rules: [{ allow: public }]) { id: ID! title: String! content: String comments: [Comment] @hasMany } type Comment @model @auth(rules: [{ allow: public }]) { id: ID! content: String postCommentsId: ID! } type Subscription { onCommentByPostId(postCommentsId: ID!): Comment @aws_subscribe(mutations: ["createComment"]) }
1type Post @model @auth(rules: [{ allow: public }]) {
2 id: ID!
3 title: String!
4 content: String
5 comments: [Comment] @hasMany
6}
7
8type Comment @model @auth(rules: [{ allow: public }]) {
9 id: ID!
10 content: String
11 postCommentsId: ID!
12}
13
14type Subscription {
15 onCommentByPostId(postCommentsId: ID!): Comment
16 @aws_subscribe(mutations: ["createComment"])
17}

Now you can create a custom subscription for comment creation with a specific post ID:

import { generateClient } from 'aws-amplify/api'; import { onCommentByPostId } from './graphql/subscriptions'; const client = generateClient(); client .graphql({ query: onCommentByPostId, variables: { postCommentsId: '12345' } }) .subscribe({ next: (data) => { console.log('data: ', data); } });
1import { generateClient } from 'aws-amplify/api';
2import { onCommentByPostId } from './graphql/subscriptions';
3
4const client = generateClient();
5
6client
7 .graphql({
8 query: onCommentByPostId,
9 variables: {
10 postCommentsId: '12345'
11 }
12 })
13 .subscribe({
14 next: (data) => {
15 console.log('data: ', data);
16 }
17 });

Unsubscribe from a subscription

You can also unsubscribe from events by using subscriptions by implementing the following:

// Stop receiving data updates from the subscription sub.unsubscribe();
1// Stop receiving data updates from the subscription
2sub.unsubscribe();

Conclusion

Congratulations! You have finished the Subscribe to real-time events guide. In this guide, you set up subscriptions for real-time events and learned how to filter and cancel these subscriptions when needed.

Next steps

Our recommended next steps include continuing to build out and customize your information architecture for your data. Some resources that will help with this work include: