What are Resolvers in AppSync and how they work
How to provide the implementation for a GraphQL schema
Resolvers in GraphQL
Resolvers provide the implementation for how AppSync processes queries and mutations. They connect the GraphQL schema, the abstract definition of the API, and the services that provide the data, such as a database or a Lambda function. In practice, most of AppSync development is spent writing resolvers.
Each resolver is for a field in the schema. And fields are defined for types. When a query requests a field, the resolver configured for that field runs and returns the appropriate data.
Let's see an example!
There is a Query
type and a test
field that is a String
. In GraphQL, it looks like this:
type Query {
test: String
}
A query then can request the test
field:
query MyQuery {
test
}
In the response, the field will be a String
, as defined in the schema:
{
"data": {
"test": "test response"
}
}
Where does this value comes from? That's what the resolver for the Query.test
field defines. It can come from a database query, a Lambda function, an HTTP
call, or some other place. The important thing is the response contains the string that AppSync will return to the query.
Nested fields
What if a field returns a type instead of a scalar? For example, the item
field returns an Item
in this schema:
type Item {
field1: String
}
type Query {
item: Item
}
A query can then request the fields of the returned type:
query MyQuery {
item {
field1
}
}
What happens here?
AppSync resolves fields recursively. It starts with the outermost field and moves down after that. Because of this, the first part is not different than
before, as it resolves Query.item
first.
The Query.item
returns some response. For AppSync, it does not matter what exactly. But after that it moves down one level to Item.field1
. The
response needs to be a String
and that will be the response for that field.
So, where is the result of the Query.item
resolver used? The Item.field1
resolver gets that as the source
object in the $context
. In
practice, most of the time the outer resolver returns an object and the inner resolver transforms part of it.
Why this structure is great
In a query, nesting can go to any depth, making it possible for the client to define exactly what it needs and AppSync does the rest. This does not requires any fewer database calls, but instead of having 1 backend call (between the client and AppSync) for each database query, it needs only 1 for the whole query. In practice, this drastically reduces the latency users experience.
As a developer, you need to think about how to generate the response for individual fields and not for individual queries. You don't need to know whether the client will need nested data or not. When a client sends a query, AppSync will run the necessary resolvers and provide the correct response with only the data the client needs.