Up and Running with GraphQL Using AWS AppSync
My previous blog post was the proclamation of my admiration of AWS AppSync as a platform for GraphQL. In this post, I wish to proceed in my tango with the Amazon service that has given immense power to developers, especially those developing APIs. Hence, this is a demo post where I shall attempt to show the very basics of getting started with AppSync, and hopefully also provide some insight to those new with the technology. At the end of it, I hope to have some extremely basic examples through which you may be able to grasp the core understanding of AppSync and especially GraphQL. This is because, as mentioned in my previous blog, AWS AppSync and GraphQL, in general, are still novel technologies and have not yet been adopted to the same extent as current conventional data-driven application development methods.
In this demo, we shall first go over the basic structure of GraphQL and then implement the learned structures in the AWS AppSync Console. Considering the different ways that one can use AppSync, I shall also try to demonstrate some of these different methods while maintaining brevity. Amazon has never faltered in producing detailed documentation of their services, and this blog is not to be an alternative to them but rather a supplement that you can use to gain a solid insight before diving into AppSync with its myriads of capabilities.
The Theory Behind It
GraphQL, the new kid on the block of querying syntax, gives great importance to the request of the client. That means the GraphQL manages to return the exact data that the client has asked for. This is achieved due to the way GraphQL is structured and how it uses a concept called “Schemas” which basically define data types. This definition of how different data types of data in terms of the object thus allows us to interact with all the data in the form of an application data graph as mentioned by Dhaivat Pandya who is a core developer at Meteor Development Group/Apollo. Hence we can visualize all data in terms of nodes and edges illustrating the interconnectivity of this type of data.
For example, in the illustration below, we can see how data regarding the animal reserves in Zambia are interconnected. And yes, as mentioned in my previous blog too, this is not the most conventional demo example but we are now sticking to it. #MakeZambiaGreatAgain. You may access the project code through the Github repository I created here.
As can be seen, each node represents a data object of a specific type and each edge is the data relation. Leaves here can be seen to be actual data that GraphQL is expected to return. Hence if we queried for all animals in the Luwangwa National Park, including only the names and not the numbers then GraphQL will first reach the Luwangwa National Park animal reserve node from the root node. From there it shall traverse only those edges that are required. So if we have not queried for the district of the reserve, then that edge will be ignored and first the `Name` and `Area` edges shall be traversed. Afterward, the `Animals` edges shall be traversed and depending on what information is needed the corresponding edges shall be traversed. This is how we can imagine GraphQL to work. Even though GraphQL is obviously more complex in its implementation, this basic understanding is imperative to how we shall design our application using GraphQL.
Schemas, Data Sources, and Other Jargon
GraphQL has three main components, and these include schemas, data sources, and resolvers.
Schemas, which are the definitions of all operations. These are objects that define the ‘nodes’ in your project when keeping the application data graph in mind. It simply involves specifying the data object name and then followed by the attributes of the object which is termed ‘fields’.
For example, the code snippet below describes how the data `Animal` is defined by a schema:
As can be seen, ‘fields’ are data attributes with the name preceding the data type. Therefore in the code snippet above, `Animal` has `id`, `name`, `number` and `reserve` as data fields, and they are followed by the data type. The exclamation mark means that when defining a data instance of `Animal` the field is required.
There are also some required schema types that are used to define the operations. These include
- Query: Used to define the operations that require user data from a data source.
- Mutation: Used to define the operations that perform any changes to the data present.
- Subscription: Used to maintain a real-time connection which sends the client data whenever a certain event occurs.
The code snippet below demonstrates how operations are defined in the schema types mentioned above:
Defining operations involves three things, defining the operation name followed by stating all the data required to perform the operation within brackets, and then followed by the data type returned that must comply with the data types defined in the schema.
Therefore, in the case of the Query operation `getAnimal` the data required to perform this operation is `id` which is of type `ID` and is required as per the exclamation mark. The datatype returned us `Animal` as can be seen in the code snippet above.
Finally, you must define to GraphQL the schema types that define the operations. This is done by using the keyword schema as shown in the code snippet below:
The overall schema can be accessed here.
To read more about Schemas in GraphQL I urge you to check out this great piece by Orjiewuru Isaac here.
Data Sources, from where the data is queried, and it is as simple as that. The real magic of working with data sources occurs via the AppSync console, which we shall discuss in later in this piece.
Other Jargon, or more specifically Resolver, act as connecting blocks between the schema operations and data sources. That means they provide the mapping logic of an operation defined in the schema to the data present in the data sources. This means that a Resolver is basically a function.
Resolvers can be quite simple to write and concise in form, but difficult to master. I personally feel that out of the three components to set up, Resolvers was the hardest to set up. However, AppSync has some great tools that allow you to automatically generate Resolvers, and we shall get to that soon.
Resolvers are per every field that requires some form of data interaction. Therefore, all operations generally have data resolvers backing them. Also, fields that have nested data types would have resolvers to ensure correct nested data is processed. Hence if we were to query an animal reserve by id, and list the details of animals living in that reserve as nested data, the `animal` field in the `Reserve` data definition within the schema would have a resolver backing it up. The resolver that achieves this is in the code snippet below:
It can be seen that the resolver `operation` type is `Scan`, as it looks through the data source, and filters the data according to the `expression` which states that the `reserve` field must ‘contain’ the data stored in the variable `:reserve`. The variable `:reserve` is retrieved from the context object passed to the resolver, and here AppSync plays a vital role. However, still pushing back introducing AppSync even though I have to it several times now, I would simply like to show by the response snippet below, that thanks to the resolver described above, we can achieve nested results.
By the way, there are no pigs randomly roaming around in the jungle reserve of Chipata, this is just data used for the demo.
Querying is extremely with GraphQL and the code snippet below describes it all.
You specify the operation type, the operation name that you would like to call upon, and then pass the data for the request within the brackets as per the schema definition of that operation. In this case, we pass the Id of the animal reserve. What follows is the data we would specifically like. From th schema definitions, we know that the operation `getReserve` returns a `Reserve` type data object. Thus, we must now define the fields of the `Reserve` type that we would like. In this case, I would only like the district of the animal reserve and then all the animals present in the reserve along with their respective information. Even though according to the schema both the `Reserve` and `Animal` data types have additional fields not requests in the query request above, they are not returned in the response thanks to GraphQL returning the exact data. Also, I shall mention AppSync here again without still not talking about it yet and state that the AppSync console makes writing these queries extremely easy.
As discussed in my previous blog I described AppSync as a platform to develop GraphQL. AWS describes it as a “serverless back-end for mobile, web, and enterprise applications”. Another point that I made was the abundance of features that AppSync provides, allowing you to do quite complex stuff as you build your data-driven applications. However, keeping with the purpose of this post which is to demonstrate the sufficient basics to get you up and running I shall focus on two features that you cannot do without.
Creating and Connecting Data Sources
Data sources as mentioned earlier is simply from where GraphQL will access the data according to the Resolvers you have written. AppSync not only allows you to connect your data sources with your resolvers with the click of a button but also allows you to automatically create them. The number of data sources offered by AppSync is quite vast, and also includes Lambda functions so basically, you can access any data store. For a detailed list of data sources please visit the AWS docs here. For this specific, not so random project, I used DynamoDB as my choice of data source. Also refer to my previous blog once again to learn about automatic code generation while creating resources through AppSync.
When creating a resource, you simply have to click on the ‘Create Resouce’ button when on the schema page to automatically generate a DynamoDB table for a specific data type. Alternatively, you can go to the ‘Data Sources’ and create a data resource from one of the many data sources available.
Next is connecting a specific data source to your project and this can be done when creating a resolver and can be seen in the illustration below:
Hence, it can be seen that when it comes to data sources AWS AppSync allows you to automatically generate data sources and effectively manage them. There is thus little coding required in creating your data sources and even less when connecting them to your Schema operations.
As I mentioned earlier, writing resolvers can be challenging sometimes. However, they can also be repetitive. Considering a large application, you would probably have several data types and have several operations to ‘get’ these resolvers. This is where one of my favorite AppSync features can be used to avoid the monotonous and tedious task. AppSync provides Resolver templates which allow you to automatically generate revolvers. Most of the templates provided by AppSync meet the lion’s share of operations that you would have to map to your data sources.
Using the templates is simply a continuation from when you connect your data source to the Schema operation as previously described.
Therefore, with these two features of AppSync, you can successfully create your first GraphQL data-driven application. Moreover, to get up and running with a proper application using APIs, you can also use Amplify which is also a relatively novel service by AWS. It can thus be concluded that AWS AppSync significantly eases development with GraphQL, and the features it hosts can allow you to achieve several things in the realm of data-driven applications. Unfortunately, as I shall end this already long post, I cannot mention it all here. Thus, I suggest that you check out the AWS AppSync documentation for greater insight. Moreover, do take a look at how you can use other data sources when developing with AppSync. A fine inquiry into the various benefits of different data sources will allow you to make better development decisions and ease the overall process. Looking at AWS AppSync and how it has simplified development, I think of Thor Heyerdahl, a great Norwegian explorer who once said that “progress is man’s ability to complicate simplicity”. However, Amazon has provided a fitting reply with AWS AppSync where progress is demonstrated in the form of simplicity.
Originally published at blog.thundra.io.