Before we dive into the details of GraphQL and AppSync, let's first see how such an API works! In this chapter, we'll deploy a social network built with technologies we'll look into in the rest of the book and see the distinguishing points why a GraphQL-based solution brings benefits.
We'll build this application step-by-step in the Example application chapter.
Download the code from here, deploy in your own account, and follow this chapter first-hand.
With the code downloaded, Terraform installed, and the AWS CLI configured, then everything is ready to deploy the app!
First, have Terraform download the modules it needs:
$ terraform init
Initializing the backend...
Initializing provider plugins...
...
Terraform has been successfully initialized!
...
Next, deploy:
$ terraform apply
...
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
It takes 5-10 minutes for everything to deploy, but then Terraform outputs the URL of the frontend:
...
aws_cloudfront_distribution.distribution: Creation complete ...
data.aws_iam_policy_document.s3_policy: Reading...
data.aws_iam_policy_document.s3_policy: Read complete after ...
aws_cognito_user_pool_client.client: Creating...
aws_s3_bucket_policy.oac: Creating...
aws_cognito_user_pool_client.client: Creation complete after ...
aws_s3_object.frontend_config: Creating...
aws_s3_object.frontend_config: Creation complete after 0s ...
aws_s3_bucket_policy.oac: Creation complete after 1s ...
Apply complete! Resources: 179 added, 0 changed, 0 destroyed.
Outputs:
domain = "d3d5eepwft4kat.cloudfront.net"
Open it in a browser and you'll be greeted with the main page:
Log in with the user:
The app shows the current user's posts, the comments for them, and the list of friends:
Then when clicking on the username of another user, its feed is shown:
So far there is nothing an app with a different technology stack couldn't do. To find the difference, we'll need to look under the hood.
On page load, there are only two dynamic requests:
Looking into the first one, we see a complex query:
{
"query":"
query MyQuery($friendsNextToken: String, $id: ID!) {
currentUser {
id
name
avatar
}
user(id: $id) {
id
name
avatar
posts {
# ...
}
# ...
}
# ...
}
",
"operationName":"MyQuery",
"variables":{"id":"550aa038-1cb0-4ef8-a085-bf6dedfda8a0"}
}
The request defines several fields to get from the backend. Then these are present in the response:
{
"data": {
"currentUser": {
"id": "a7ded6f4-11c7-4f0d-aa48-57afb64f9bf6",
"name": "Test User",
"avatar": "..."
},
"user": {
"id": "550aa038-1cb0-4ef8-a085-bf6dedfda8a0",
"name": "Lisa Ondricka",
"avatar": "...",
"posts": {
"nextToken": "...",
"posts": [
// ...
]
}
}
}
}
This provides an efficient way to communicate between the frontend and the backend: by sending a complex query the client can define exactly what it needs and the backend prepares the response without wasting roundtrips. Roundtrips are the slowest part of a frontend, and minimizing them provides an enormous speedup for webapps.
Unlike the backend-for-frontend approach where the backend provides a special endpoint for each client where each endpoint returns only the data that client needs to display, GraphQL clients use a single API. This makes the backend development easier as it does not need to keep the client in mind: define what data is available and let the clients send queries with what they need.
And what's the second request? It's pagination, a thing that we'll cover later in the book.
Looking around the in Network panel, you can find another interesting bit: a WebSocket connection that sends new posts and comments in real-time:
Looking further, we'll see the same familiar syntax for real-time updates as for synchronous queries:
{
"query":"
subscription MySubscription($userId: ID!) {
post(userId: $userId) {
post {
date
id
text
}
}
}
",
"variables":{"userId":"550aa038-1cb0-4ef8-a085-bf6dedfda8a0"}
}
AppSync provides this WebSocket endpoint and also handles all connections and sending messages to clients. This makes the solution trivially easy to operate: it works the same with one client as with a thousand.
And a shared WebSocket channel also makes things lighter on the frontend: in the ideal case, a webapp requires only one fetch and one WebSocket connection. No extra roundtrips, no additional connections.
After the client-side benefits, let's see what resources we have on the AWS-side!
There is an AppSync API, with a schema and the implementations of the various parts required according to GraphQL:
The API integrates with DynamoDB to read and write data:
And users are stored in a Cognito User Pool:
This provides a truly serverless solution: no part requires managing instances or planning capacity. AppSync integrates with many AWS services, making it possible to run Lambda functions to serve queries, or make HTTP requests in response to data changes. We'll discuss how in the book.
When you're done examining the example project, don't forget to remove it:
$ terraform destroy
...
Plan: 0 to add, 0 to change, 179 to destroy.
Changes to Outputs:
- domain = "d3d5eepwft4kat.cloudfront.net" -> null
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure,
as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
...
random_id.id: Destroying... [id=sqGOdyTlSow]
random_id.id: Destruction complete after 0s
aws_s3_bucket.frontend_bucket: Destruction complete after 1s
Destroy complete! Resources: 179 destroyed.