Anatomy of an AppSync resolver
How request and response mapping work in AppSync
Resolvers define how to provide the data for a GraphQL field. They provide the implementation behind the schema and as such they are important to get right.
In this article, we're going to take a detailed look into what happens in a resolver when it provides a value that AppSync can return to a query.
Data source
A resolver needs a data source. This defines what other service AppSync will use when it resolves the field. For example, the DynamoDB data source will send a request to a table, the HTTP data source will send a request to an arbitrary URL, and a Lambda data source calls a function.
The data source defines the request and the response formats. Fetching an item from a DynamoDB table requires different parameters than calling a Lambda function.
For example, to send a DynamoDB GetItem request using a DynamoDB data source requires this structure:
{
"version" : "2017-02-28",
"operation" : "GetItem",
"key" : {
"foo" : "... typed value",
"bar" : "... typed value"
},
"consistentRead" : true
}
And its response contains the item with its fields converted to plain JSON types:
{
"id": "hash key",
"number": 15,
"another_value": "test"
}
On the other hand, the HTTP data source requires a different request format:
{
"version": "2018-05-29",
"method": "PUT|POST|GET|DELETE|PATCH",
"params": {
"query": {},
"headers": {},
"body": "string"
},
"resourcePath": "string"
}
And its response contains the response body, the status code and a few other fields:
{
"statusCode": 200,
"body": "Test body"
}
The AppSync reference contains the expected request and response format for each data source.
The data source also configures how the target service can be used. For example, a DynamoDB data source needs to know which table to query, and what IAM role to use that has the necessary permissions. An HTTP data source needs the URL to call, and a Lambda data source needs to know the function to call.
Mapping templates
Mapping templates convert the AppSync query to the request format the data source requires as well as convert the data source response to the format AppSync expects for the field.
Both mapping templates get a context
variable that contains information about the request and the response. AppSync uses VTL (Velocity Template Language)
that gets this context and the template and transforms it to the result format.
AppSync provides some shorter alternatives for accessing the context
. The $ctx
is the same as $context
, and $ctx.args
is the same
as $context.arguments
.
Request template
Let's see an example!
There is a query that gets an ID and returns a User object:
type User {
id: ID
name: String
}
type Query {
user(id: ID): User
}
schema {
query: Query
}
The resolver for the Query.user
field gets an id
argument and uses a DynamoDB table as the data source. In the request mapping template, the
ctx.args.id
is the ID of the user the query asks for and because of the data source it needs to produce the GetItem
structure described above.
Here's the request template for that resolver:
{
"version" : "2018-05-29",
"operation" : "GetItem",
"key" : {
"id": {"S": $util.toJson($ctx.args.id)}
},
"consistentRead" : true
}
Let's see what happens for a query!
query MyQuery {
user(id: "user1@example.com") {
id
name
}
}
When the request mapping template runs, $ctx.args.id
will be "user1@example.com". So when AppSync transforms the template, the result will be:
{
"version" : "2018-05-29",
"operation" : "GetItem",
"key" : {
"id": {"S": "user1@example.com"}
},
"consistentRead" : true
}
You can inspect how the process works when AppSync logging is turned on. Notice the context
and the transformedTemplate
properties:
This matches what the DynamoDB data source expects, so it sends a request to the table (using an IAM role). When the item is fetched, it returns the object as JSON.
Response template
In the response mapping template, there will be two extra fields in the context
object:
$ctx.error
that indicates that DynamoDB returned an error if present$ctx.result
is the result object
AppSync won't return an error just because the underlying data source returned one, so the response template needs to check the $ctx.error
and throw it if
it is defined. Otherwise, it can return the $ctx.result
:
#if ($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end
$util.toJson($ctx.result)
How it works can be observed in the logs. Notice the context.result
field and the transformedTemplate
:
Conclusion
Resolvers are strongly tied to the data source configured for them. Data sources define the request and the response formats that AppSync needs to use.
The mapping templates define the transformation from the AppSync context to what the data source defines.