Data sources

In GraphQL, a resolver is a function that gets the arguments and the context for a field and returns a value. This is a generic concept and applies to all GraphQL servers. But how this function is implemented, such as what language it can be written in, can be different between servers.

clientGraphQLbackendResolverquery MyQuery {groupById(id: "group1") {idname}}resolve groupById(id: "group1")Resolve field...{"data": {"id": "group1","name": "Group 1"}}
Field resolving in GraphQL

In AppSync, a resolver is broken into three parts:

  • the request mapping template
  • the data source
  • and the response mapping template

The request mapping template converts the GraphQL context into a format suitable for the data source. Then the data source interacts with some external service, such as a database, a Lambda function, or sends an HTTP request. Then the response mapping template gets the result of the data source and converts it into a format suitable for the resolver's result.

clientGraphQLbackendResolverData sourcequery MyQuery {groupById(id: "group1") {idname}}resolve groupById(id: "group1")Request mappingtemplaterequestresultResponse mappingtemplate{"data": {"id": "group1","name": "Group 1"}}
Field resolving in AppSync

The mapping templates are written in Velocity Template Language (VTL), which is a Java-based templating language. Since both the data source's input format and the resolver's result are JSON, the templates need to produce a valid JSON file.

Note

Velocity is a general-purpose template language. It gets some arguments, in the case of AppSync the resolver context, and returns text. For an AppSync resolver, this text needs to be parseable to JSON.

For example, to get an item from a DynamoDB table, the request mapping template for the groupById resolver can use the id argument:

{
  "version": "2018-05-29",
  "operation": "GetItem",
  "key": {
    "id": {"S": $util.toJson($ctx.args.id)}
  }
}

When a query comes for this field, such as this one:

query MyQuery {
  groupById(id: "group1") {
    id
    name
  }
}

The resulting JSON will be:

{
  "version": "2018-05-29",
  "operation": "GetItem",
  "key": {
    "id": {"S": "group1"}
  }
}

The DynamoDB data source knows how to process this input, and fetches an item from the configured table with the id of group1.

Then the response mapping template needs to convert the item to the resolver's result format:

#if($ctx.error)
  $util.error($ctx.error.message, $ctx.error.type)
#end
$util.toJson($ctx.result)

Let's say the item is a JSON with the id and a name property:

{
  "id": "group1",
  "name": "Group 1"
}

The response mapping template gets the above JSON as the $ctx.result and returns this JSON:

{
  "id": "group1",
  "name": "Group 1"
}

Which is in a format that the result type can use.

Running the resolver
requestrequest mapping template{"version": "2018-05-29","operation": "GetItem","key": {"id": {"S": $util.toJson($ctx.args.id)}}}data sourceresponse mapping template#if($ctx.error)$util.error($ctx.error.message, $ctx.error.type)#end$util.toJson($ctx.result)resultgroupById(id: "group1"){"version": "2018-05-29","operation": "GetItem","key": {"id": {"S": "group1"}}}{"id": "group1","name": "Group 1"}{"id": "group1","name": "Group 1"}
AppSync resolver flow

Why AppSync implements this separation?

Usually, data sources have parameters that the resolver might not know, such as the DynamoDB table. In the above example, only the id of the item is defined, but not the table name. That comes from the data source config.

Even more important is that the data source configuration can define the IAM role AppSync assumes when it calls the external service. In AWS, the services usually don't have access to resources in the account. For example, the AppSync API by default can not read data from any DynamoDB table. The API gains the necessary permissions through assuming a role and using its permissions. In a sense, the role is the connecting piece between the API and the resource.

AppSync APIDynamoDB TableIAM RoleTrust PolicyPermissions Policy{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Service":"appsync.amazonaws.com"},"Action": "sts:AssumeRole"}]}{"Effect": "Allow","Action": ["dynamodb:Query",],"Resource": ["arn:aws:dynamodb:..."}
AppSync gets access to the table via an IAM Role

To configure permissions, first create an IAM role and define that AppSync will use it in its trust policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "appsync.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Then give access in the role's permissions policy to what resources the data source needs:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": [
        "dynamodb:Query",
      ],
      "Resource": [
        "arn:aws:dynamodb:...:table/..."
      ]
    },
  ]
}

Finally, configure the role for the data source:

The IAM role can be configured for a DynamoDB data source
Note

You can configure a different role for each data source. This makes it possible to segment the permissions, for example, give only access to one table per data source.

So, the full flow for resolving a field:

  • AppSync runs the request mapping template that produces a JSON
  • it assumes the role configured for the data source and gets temporary credentials
  • invokes the data source
  • then it runs the response mapping template passing the data source's result as the $ctx.result
  • the result is the JSON that the template produces
Note

Data sources are AppSync-related APIs that interact with external resources.

What data sources are available?

The documentation page shows a list of them:

Documentation page for the data sources

And to see what structure each data source needs and what it returns check the individual documentation page (for example, this for DynamoDB).

In the next chapters we'll see how some of the most useful data sources work.

Master AppSync and GraphQL
Support this book and get all future updates and extra chapters in ebook format.