Building Real-Time Notifications with AWS AppSync: From GraphQL to WebSockets
Real-time applications are becoming the norm, from chat systems to live dashboards. Discover how AWS AppSync simplifies the development of real-time solutions using GraphQL subscriptions and WebSockets. Learn the basics of GraphQL, resolvers, and data sources, and follow a practical guide to building a notification system. Ideal for anyone looking to implement efficient, scalable, and secure real-time functionality.
Published Dec 23, 2024
Before we dive into implementing a real-time solution with AWS AppSync , it’s important to understand the key concepts behind this technology. Here we explain the essential elements in a clear and simple way:
GraphQL is a query language for APIs that enables clients to request exactly the data they need—no more, no less. Designed to address the limitations of traditional REST APIs, GraphQL offers:
- Flexibility : You can get data from multiple resources in a single request.
- Efficiency : Avoids problems with “overfetching” (getting more data than needed) or “underfetching” (needing multiple requests to complete a task).
- Predictable structure : The client defines how the data will look in the response, which facilitates frontend development.
An example GraphQL query to get task information might look like this:
The response would have the same structure as you defined in the query:
A resolver is the logic that connects your GraphQL schema to the actual data source. In other words, it’s the “bridge” that translates a GraphQL query or mutation into concrete operations in your backend, such as a database query or the execution of a Lambda function.
In AppSync , resolvers are configured using mapping templates written in VTL (Velocity Template Language). For example, a resolver to get tasks from a DynamoDB table might look like this:
This resolver tells AppSync to perform an operation
Scan
on the DynamoDB table named TasksTable
.A data source is any location from which AppSync can obtain or store data. Some common examples are:
- DynamoDB : Ideal for structured and fast-access data.
- AWS Lambda : For custom logic or access to external services.
- RDS : For relational databases.
- HTTP Endpoints : To interact with external APIs.
Each data source is associated with one or more resolvers. For example:
- A query
getTasks
can use DynamoDB as a data source. - A mutation
createTask
can use Lambda to execute more complex logic.
WebSocket is a protocol that allows real-time, two-way communication between client and server. Unlike traditional HTTP requests, where the client must initiate all interactions, WebSocket maintains an open connection that allows the server to send updates to the client at any time.
This makes it perfect for applications such as:
- Real-time notifications.
- Online chats.
- Dashboards with instant updates.
In AppSync , GraphQL subscriptions use WebSockets to notify connected clients when changes occur to data related to their subscription.
It is a technique in which the client makes periodic requests to the server to check if there are any changes in the data.
Advantages:
- Easy to implement with standard HTTP requests.
- Does not require a persistent connection.
Disadvantages:
- Inefficient : Consumes more client and server resources due to repetitive requests.
- Latency : Data is only updated at predefined time intervals.
It is a protocol that allows maintaining a bidirectional and persistent connection between the client and the server.
Advantages:
- Efficiency : Data is sent only when there are changes, reducing the load on the server.
- Real-time : Allows instant updates without the need for query intervals.
- Disadvantages:
- Greater complexity in implementing and managing connections.
- Requires infrastructure that supports WebSockets.
With polling , the client constantly “asks” for updates, while with WebSockets , the server “notifies” the client when something relevant happens.
It is an architectural style where resources are exposed as endpoints (URLs) and interacted with using HTTP methods such as
GET
, POST
, PUT
and DELETE
.Advantages:
- Standardized and widely adopted.
- Cacheable: Supports HTTP-based caching mechanisms.
Disadvantages:
- “Overfetching” and “Underfetching”: The client may receive more data than necessary or require multiple calls to obtain all the required data.
- Difficulty in evolving: Changing the resource structure can affect multiple clients.
It is a query language that allows clients to define exactly what data they need in a single request.
Advantages:
- Flexibility : A single query can retrieve data from multiple resources.
- Evolutionary : Changes to the schema do not break existing customers.
- Efficiency : Reduces unnecessary requests to the server.
Disadvantages:
- Higher learning curve for unfamiliar teams.
- It may be less efficient in simple operations compared to REST.
Key Difference:
- REST delivers fixed data in a predefined format by the server, while GraphQL allows clients to specify what data they need and in what structure.
AWS AppSync is a managed service that makes it easy to build scalable, secure, real-time GraphQL APIs. Its main goal is to simplify the interaction between applications and data sources, providing an all-in-one solution for building modern applications.
GraphQL Serverless : Run GraphQL APIs without having to manage servers.
AWS Native Data Sources : DynamoDB, Lambda, S3, RDS, among others.
WebSockets and Real-Time Support : Implement GraphQL subscriptions with WebSockets for instant notifications.
Scalability – AppSync automatically scales to handle hundreds or thousands of simultaneous connections.
Amplify Integration : Allows for easy setup and use on web and mobile applications.
Cross-Platform Compatibility : Works with any standard GraphQL client.
API Key:
- It is used for development or testing.
- Less secure, since any client with the key can access the API.
AWS IAM:
- Requires authentication using AWS roles or users.
- Ideal for backend integrations or server-to-server services.
Cognito User Pools:
- Provides user-based authentication.
- Perfect for applications with end-user registration.
OpenID Connect (OIDC):
- Allows you to integrate external identity providers.
- Useful for applications that use custom authentication.
Lambda Authorizer:
- Execute custom authentication logic in a Lambda function.
- Ideal for complex scenarios where you need specific rules.
Imagine you are developing a productivity app where users can manage tasks as a team. One of the key requirements is that any team member receives real-time notifications when a new task is created, without the need to manually refresh the app.
- If a user creates a new task, all team members should receive an instant notification on their devices, indicating that the task has been created.
- This includes users connected from multiple platforms (web, mobile, tablet).
To implement this functionality, you might consider the following options:
Polling:
Configure the application to query the server periodically for new tasks.
Problem: This creates a high load on the server, is inefficient, and does not provide instant updates (there may be noticeable latency).
REST with Push Notifications:
Using a REST backend along with push notifications to send updates.
Problem: Push notifications depend on external services (such as Firebase for mobile) and are not ideal for web applications or cross-platform scenarios.
Custom WebSockets:
Implement a solution using WebSockets to maintain a persistent connection between client and server.
Problem: Requires managing WebSockets infrastructure, connections, reconnections, and scalability manually, which can be complex and expensive.
AWS AppSync solves this problem elegantly using WebSockets-based GraphQL subscriptions. With this solution:
- Persistent Connection: Clients establish a WebSocket connection with AppSync to receive real-time notifications.
- Automatic Notifications: When a user creates a new task, AppSync automatically notifies all subscribed clients.
- Scalability: AppSync automatically handles scalability and connection management, removing infrastructure complexity.
- Cross-platform: Works perfectly on web, mobile, or any GraphQL-compatible client.
Below, I walk you through how to implement a real-time notification system using AWS AppSync . Each step includes specific instructions on what to do in the AWS console, how to configure resolvers and data sources, and what code to add:
- Go to the AppSync consolein AWS.
- Click the “Create API” button .
- Select “Build from scratch” and give your API a name, such as
TaskManagementAPI
.
Step 2: Set up the initial GraphQL schema:
In the “Schema” section , define the following basic schema for handling tasks and notifications:
Initial scheme:
This scheme defines:
- A guy
Task
who represents tasks. - A mutation
createTask
to add new tasks. - A subscription
onTaskCreated
that is activated every time you runcreateTask
. - A query
getTasks
to get all tasks (optional).
Create a DynamoDB table:
- Go to the DynamoDB console.
- Click “Create Table” .
- Assign the name
TasksTable
. - Set the Primary Key to
id
(String type). - Create at the blackboard.
Configure DynamoDB as a Data Source in AppSync:
- Return to the AppSync console.
- In the “Data Sources” section, click “Create Data Source” .
- Select “Amazon DynamoDB Table” .
- Assign a name, such as
TasksDynamoDB
. - Select the table
TasksTable
you created in the previous step. - Click “Create” .
Solve for
createTask
- Go to the “Schema” section.
- In the mutation
createTask
, click "Attach Resolver" . - Configure a resolver with the following mapping template (VTL):
Request Mapping Template:
Response Mapping Template:
Solve for
onTaskCreated
- The subscription does not require a manual resolver. AppSync automatically configures it by linking the mutation
createTask
in the schema with@aws_subscribe
.
Solve for
getTasks
- In the query
getTasks
, click "Attach Resolver" . - Configure a resolver with the following mapping template:
Request Mapping Template:
Response Mapping Template:
Step 5: Test the API from the Console
- Go to the “Queries” sectionin the AppSync console.
Run the mutation to create a task:
Set up a subscription in the console:
- From another browser tab or GraphQL client, run the mutation
createTask
. You will see the subscription receive the task in real time.
Now let's increase the complexity of the exercise:
- We will have tasks that are grouped by an identifier
- When there is a subscription, this subscription must be made by the grouping identifier
- When a new task is created, if there is an active subscription for that grouper ID, the notification should be seen
Modify the GraphQL schema so that the subscription accepts an argument
boardId
. This argument will allow task notifications to be filtered based on the board.boardId
in the task: All tasks are associated with a board ( boardId
).boardId
Subscriptionargument : Customers can subscribe only to tasks created on a specific board.Relationship between
createTask
and onTaskCreated
: The subscription onTaskCreated
is triggered only when the mutation is executed createTask
.You don't need to manually configure a resolver for the subscription because AppSync handles this automatically with the annotation
@aws_subscribe
.However, AppSync uses internal filters for subscriptions. When a client subscribes, AppSync automatically filters events based on the
boardId
.Execution of the subscription:
Variables:
We create a new task associating the subscription's boardId:
The result of the creation should be this:
And from where we are making the subscription we should see the generated event:
References:
If you liked this article, don't hesitate to give it a 👏
- youtube: https://www.youtube.com/jjoc007
- github: https://github.com/jjoc007
- Medium: https://jjoc007.com
- Linkedin: https://www.linkedin.com/in/jjoc007/