real-time GraphQL
Hi, welcome to real-time GraphQL! This page will give you an introduction about creating real-time GraphQL applications using AWS AppSync, Next.js and Apollo Client.
If you click the button in the top right corner, you will be taken to a real-time GraphQL chat application. After loggin in, you can start chatting in multiple browsers / on multiple devices and see your changes being updated everywhere, in real-time.
The following sections explain how the chat app works under the hood.
You will learn
- How to setup infrastructure to create serverless GraphQL applications
- How to scale the frontend by using GraphQL fragment colocation
- How to enable real-time data communication by using GraphQL subscriptions
- How to manage GraphQL data on the frontend and automatically update the UI using Apollo Client
Cloud infrastructure
The following architecture diagram gives you an overview about the infrastructure of the chat app.
It shows 3 AWS AppSync APIs (User-API, Chat-API and the File-API), each with their own graphql schema. The backend exposes one unified, merged graphql schema (accessible via a custom Route53 domain). The merged API handles request routing to the correct sub-APIs. The merged API is an AWS AppSync API by itself.
Chatmessages, user data and chatroom data is stored in MongoDB Atlas. Files and other blob data is stored in S3, exclusively accessible through the File-API.
Data receiving and data manipulation is entirely done serverless, using AWS lambda resolvers. The whole backend is deployed to the AWS cloud via infrastructure as code, by leveraging AWS CDK.
User authentication and authorization is handled by AWS Cognito in combination with NextAuth.
The frontend is a Next.js app that uses Apollo Client to interact with the backend. Apollo Client automatically stores data on the frontend and updates the UI if data changes.
GraphQL fragment colocation on the frontend
GraphQL is a perfect technology for large, data intensive apps, as it provides a scalable way to avoid over- and underfetching through fragment colocation.
Overfetching occurs when you get more data in a single request than your application actually needs.
Underfetching happens when insufficient data is received in a single request. In such case, the application has to perform another sequential request to get all required data, resulting in longer loading times.
With GraphQL fragment colocation, you can exactly define the data that the current visible page needs to render, leading to fewer round trips, a smaller overall payload and faster loading times.
The following example is from the chat app itself: on the left side is a ChatList React component with ChatLisItems. In the center of the app is the ChatArea component. Each component has its own data requirements.

query ChatPage($input: InputQueryChatRooms) {chatRooms(input: $input) {...ChatListChatRoom...ChatAreaChatRoom}}fragment ChatListChatRoom on ChatRoom {...ChatListItemChatRoom}fragment ChatListItemChatRoom on ChatRoom {idnameimage}fragment ChatAreaChatRoom on ChatRoom {...}
By using GraphQL fragments, you can define the data a component needs to render directly inside the component itself. This makes components more reusable and easier to maintain. And since apps have a nested component structure and nested rendering tree, graphql fragments can be nested as well by including child fragments in parent fragments (same as child components are included in parent components).
In order for this technique to work, each fragment must have a unique name. Over the years, a few naming conventions established themselve. One very popular is to use the component name plus the GraphQL entity.
For example, if the component name is "ChatListItem" and the GraphQL entity name is "ChatRoom", the fragment name will become "ChatListItemChatRoom". If the GraphQL entity is "User", the fragment name will be "ChatListItemUser".
Which seems a little bit verbose in the beginning will scale very well in large apps and effectivly avoids name collisions. An additional benefit is that this convention makes it easy to understand which component defines which fragment.
Real-time data updates through GraphQL subscriptions
To be informed about data changes, the client can subscribe to events which are happening on the server. Whenever a subscribed event occurs, the server sends updated data to the client, without the need for constant polling. This real-time data communication opens up new possibilities for data driven, responsive, collaborative applications like chat apps, social media platforms, stock exchanges and many more.
AWS AppSync supports GrapQL subscriptions out of the box. The only thing that the client needs to do is to subscribe to an event. The service will take care of the rest. Custom resolver logik can be used to authenticate and authorize the client before sending any data.
The following example shows how to subscribe to an update of a chatroom. Whenever the updateChatroom mutation is executed on the server, the server will send the updated chatroom data to all subscribed clients.
input InputMutationUpdateChatRoom {id: String!name: Stringdescription: String}type Mutation {updateChatRoom(input: InputMutationUpdateChatRoom!): ChatRoom}type Subscription {onUpdateChatRoom(id: String!): ChatRoom@aws_subscribe(mutations: ["updateChatRoom"])}
Automatic UI updates with Apollo Client
Apollo Client is an excellent solution for managing client-side data in modern applications. Among its many advanced features, it includes an in-memory cache for efficient client side data storage. The cache automatically updates whenever new data is retrieved from the server, seamlessly triggering UI updates. Additionally, Apollo Client offers extensive customization options like middlewares, custom data storage or different fetch policies, while delivering great functionality right out of the box.
To reduce storage size and avoid data redundancy, Apollo Client uses a normalized, flat cache, which stores each GraphQL entity exactly once. Reference pointers are used in nested data structures to link to other cached objects.
Take a look at the following example. A "ChatRoom" object contains an array of "messages", which are reference pointers to other "Message" objects in the cache. Each message was created by an "User" and has therefore a reference to the "User" object in the cache.
# ChatRoom object
{"__typename": "ChatRoom","id": "0819c202-682d-47ed-8af1-78aa2e1ea8cb","name": "planes","messages": [{"__ref": "Message:b7de17b4-92b8-4153-a611-049a902c8531"},]}
# Message object
{"__typename": "Message","id": "b7de17b4-92b8-4153-a611-049a902c8531","text": "I'm Alice and I like the chat room: planes","user": {"__ref": "User:94289478-b0b1-70b4-2e32-eabb43006ec4"}}
# User object
{"__typename": "User","id": "94289478-b0b1-70b4-2e32-eabb43006ec4","nickName": "Alice",}
Apollo Client offers several options for updating cached data, providing the flexibility to accommodate various use cases. The simplest approach is to define the response when performing a query or mutation. Apollo Client will automatically update the cache with new data and triggers an UI update. If you need to update the cache manually, for example when subscribing to events, you can use cache.updateQuery, cache.updateFragment, cache.modify or cache.evict. This versatility makes Apollo Client highly adaptable.
Want to know more?
You got a small peek into the world of real-time GraphQL applications. If you want to know more, feel free to reach out to us. We offer consulting and development services for market leaders around the world. Our clients are from various industries like IT-Security, finance, healthcare, biotechnology, the energy sector and many more.
Visit us at https://www.bitvance.com or write us consulting@bitvance.com