Created
April 13, 2018 16:01
-
-
Save adrianhall/50e9fdf08e7a7e52d3ab0f01467b72f7 to your computer and use it in GitHub Desktop.
An example CloudFormation template for AWS AppSync
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
Description: AWSAppSync DynamoDB Example | |
Resources: | |
GraphQLApi: | |
Type: "AWS::AppSync::GraphQLApi" | |
Properties: | |
Name: AWSAppSync DynamoDB Example | |
AuthenticationType: AWS_IAM | |
PostDynamoDBTableDataSource: | |
Type: "AWS::AppSync::DataSource" | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
Name: PostDynamoDBTable | |
Description: The Post DynamoDB table in us-west-2 | |
Type: AMAZON_DYNAMODB | |
ServiceRoleArn: "arn:aws:iam::YOUR_ACCOUNT_ID:role/AppSyncTutorialAmazonDynamoDBRole" | |
DynamoDBConfig: | |
AwsRegion: "us-west-2" | |
TableName: "AppSyncTutorial-Post" | |
QueryGetPostResolver: | |
Type: "AWS::AppSync::Resolver" | |
DependsOn: Schema | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
TypeName: Query | |
FieldName: getPost | |
DataSourceName: !GetAtt PostDynamoDBTableDataSource.Name | |
RequestMappingTemplate: | | |
{ | |
"version" : "2017-02-28", | |
"operation" : "GetItem", | |
"key" : { | |
"id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) | |
} | |
} | |
ResponseMappingTemplate: "$utils.toJson($ctx.result)" | |
QueryAllPostResolver: | |
Type: "AWS::AppSync::Resolver" | |
DependsOn: Schema | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
TypeName: Query | |
FieldName: allPost | |
DataSourceName: !GetAtt PostDynamoDBTableDataSource.Name | |
RequestMappingTemplate: | | |
{ | |
"version" : "2017-02-28", | |
"operation" : "Scan", | |
#if( $ctx.args.count ) | |
"limit": $ctx.args.count, | |
#end | |
#if( ${ctx.args.nextToken} ) | |
"nextToken": "${ctx.args.nextToken}" | |
#end | |
} | |
ResponseMappingTemplate: | | |
{ | |
"posts": $utils.toJson($ctx.result.items), | |
#if( ${ctx.result.nextToken} ) | |
"nextToken": "${ctx.result.nextToken}", | |
#end | |
} | |
QueryAllPostsByAuthorResolver: | |
Type: "AWS::AppSync::Resolver" | |
DependsOn: Schema | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
TypeName: Query | |
FieldName: allPostsByAuthor | |
DataSourceName: !GetAtt PostDynamoDBTableDataSource.Name | |
RequestMappingTemplate: | | |
{ | |
"version" : "2017-02-28", | |
"operation" : "Query", | |
"index" : "author-index", | |
"query" : { | |
"expression": "author = :author", | |
"expressionValues" : { | |
":author" : $util.dynamodb.toDynamoDBJson($ctx.args.author) | |
} | |
}, | |
#if( $ctx.args.count ) | |
"limit": $ctx.args.count, | |
#end | |
#if( ${ctx.args.nextToken} ) | |
"nextToken": "${ctx.args.nextToken}", | |
#end | |
} | |
ResponseMappingTemplate: | | |
{ | |
"posts": $utils.toJson($ctx.result.items), | |
#if( ${ctx.result.nextToken} ) | |
"nextToken": "${ctx.result.nextToken}", | |
#end | |
} | |
QueryAllPostsByTagResolver: | |
Type: "AWS::AppSync::Resolver" | |
DependsOn: Schema | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
TypeName: Query | |
FieldName: allPostsByTag | |
DataSourceName: !GetAtt PostDynamoDBTableDataSource.Name | |
RequestMappingTemplate: | | |
{ | |
"version" : "2017-02-28", | |
"operation" : "Scan", | |
"filter": { | |
"expression": "contains (tags, :tag)", | |
"expressionValues": { | |
":tag": $util.dynamodb.toStringJson($ctx.args.tag) | |
} | |
}, | |
#if( $ctx.args.count ) | |
"limit": $ctx.args.count, | |
#end | |
#if( ${ctx.args.nextToken} ) | |
"nextToken": "${ctx.args.nextToken}" | |
#end | |
} | |
ResponseMappingTemplate: | | |
{ | |
"posts": $utils.toJson($ctx.result.items), | |
#if( ${ctx.result.nextToken} ) | |
"nextToken": "${ctx.result.nextToken}", | |
#end | |
} | |
MutationaddPostResolver: | |
Type: "AWS::AppSync::Resolver" | |
DependsOn: Schema | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
TypeName: Mutation | |
FieldName: addPost | |
DataSourceName: !GetAtt PostDynamoDBTableDataSource.Name | |
RequestMappingTemplate: | | |
#set( $d = $util.dynamodb ) | |
#set( $values = $d.toMapValues($ctx.args)) | |
$!{values.put("ups", $d.toNumber(1))} | |
$!{values.put("downs", $d.toNumber(0))} | |
$!{values.put("version", $d.toNumber(1))} | |
$!{values.put("created", $d.toDynamoDB($util.time.nowISO8601()))} | |
$!{values.put("lastUpdated", $values.get("created"))} | |
{ | |
"version" : "2017-02-28", | |
"operation" : "PutItem", | |
"key" : { | |
"id" : $d.toStringJson($utils.autoId()) | |
}, | |
"attributeValues" : $util.toJson($values), | |
} | |
ResponseMappingTemplate: "$util.toJson($ctx.result)" | |
MutationAddCommentResolver: | |
Type: "AWS::AppSync::Resolver" | |
DependsOn: Schema | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
TypeName: Mutation | |
FieldName: addComment | |
DataSourceName: !GetAtt PostDynamoDBTableDataSource.Name | |
RequestMappingTemplate: | | |
{ | |
"version" : "2017-02-28", | |
"operation" : "UpdateItem", | |
"key" : { | |
"id" : $util.dynamodb.toStringJson($ctx.args.id) | |
}, | |
"update" : { | |
"expression" : "SET comments = list_append(if_not_exists(comments, :emptyList), :newComment), lastUpdated = :lastUpdated ADD version :plusOne", | |
"expressionValues" : { | |
":emptyList": $util.dynamodb.toListJson([]), | |
":newComment" : $util.dynamodb.toListJson([$util.map.copyAndRetainAllKeys($ctx.args, ["author","comment"])]), | |
":plusOne" : $util.dynamodb.toNumberJson(1), | |
":lastUpdated" : $util.dynamodb.toDynamoDBJson($util.time.nowISO8601()) | |
} | |
}, | |
"condition" : { | |
"expression" : "attribute_exists(id)" | |
}, | |
} | |
ResponseMappingTemplate: "$util.toJson($ctx.result)" | |
MutationAddTagResolver: | |
Type: "AWS::AppSync::Resolver" | |
DependsOn: Schema | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
TypeName: Mutation | |
FieldName: addTag | |
DataSourceName: !GetAtt PostDynamoDBTableDataSource.Name | |
RequestMappingTemplate: | | |
{ | |
"version" : "2017-02-28", | |
"operation" : "UpdateItem", | |
"key" : { | |
"id" : $util.dynamodb.toStringJson($ctx.args.id) | |
}, | |
"update" : { | |
"expression" : "ADD tags :tags, version :plusOne SET lastUpdated = :lastUpdated", | |
"expressionValues" : { | |
":tags" : $util.dynamodb.toStringSetJson([$ctx.args.tag]), | |
":plusOne" : $util.dynamodb.toNumberJson(1), | |
":lastUpdated" : $util.dynamodb.toDynamoDBJson($util.time.nowISO8601()), | |
} | |
}, | |
"condition" : { | |
"expression" : "attribute_exists(id)" | |
}, | |
} | |
ResponseMappingTemplate: "$util.toJson($ctx.result)" | |
MutationRemoveTagResolver: | |
Type: "AWS::AppSync::Resolver" | |
DependsOn: Schema | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
TypeName: Mutation | |
FieldName: removeTag | |
DataSourceName: !GetAtt PostDynamoDBTableDataSource.Name | |
RequestMappingTemplate: | | |
{ | |
"version" : "2017-02-28", | |
"operation" : "UpdateItem", | |
"key" : { | |
"id" : $util.dynamodb.toStringJson($ctx.args.id) | |
}, | |
"update" : { | |
"expression" : "DELETE tags :tags ADD version :plusOne SET lastUpdated = :lastUpdated", | |
"expressionValues" : { | |
":tags" : $util.dynamodb.toStringSetJson([$ctx.args.tag]), | |
":plusOne" : $util.dynamodb.toNumberJson(1), | |
":lastUpdated" : $util.dynamodb.toDynamoDBJson($util.time.nowISO8601()), | |
} | |
}, | |
"condition" : { | |
"expression" : "attribute_exists(id)" | |
}, | |
} | |
ResponseMappingTemplate: "$util.toJson($ctx.result)" | |
MutationDeletePostResolver: | |
Type: "AWS::AppSync::Resolver" | |
DependsOn: Schema | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
TypeName: Mutation | |
FieldName: deletePost | |
DataSourceName: !GetAtt PostDynamoDBTableDataSource.Name | |
RequestMappingTemplate: | | |
{ | |
"version" : "2017-02-28", | |
"operation" : "DeleteItem", | |
"key": { | |
"id": $util.dynamodb.toStringJson($ctx.args.id) | |
}, | |
#if( $ctx.args.containsKey("expectedVersion") ) | |
"condition" : { | |
"expression" : "attribute_not_exists(id) OR version = :expectedVersion", | |
"expressionValues" : { | |
":expectedVersion" : $util.dynamodb.toNumberJson($ctx.args.expectedVersion) | |
} | |
}, | |
#end | |
} | |
ResponseMappingTemplate: "$util.toJson($ctx.result)" | |
MutationUpvotePostResolver: | |
Type: "AWS::AppSync::Resolver" | |
DependsOn: Schema | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
TypeName: Mutation | |
FieldName: upvotePost | |
DataSourceName: !GetAtt PostDynamoDBTableDataSource.Name | |
RequestMappingTemplate: | | |
{ | |
"version" : "2017-02-28", | |
"operation" : "UpdateItem", | |
"key" : { | |
"id" : $util.dynamodb.toStringJson($ctx.args.id) | |
}, | |
"update" : { | |
"expression" : "ADD ups :plusOne, version :plusOne SET lastUpdated = :lastUpdated", | |
"expressionValues" : { | |
":plusOne" : $util.dynamodb.toNumberJson(1), | |
":lastUpdated" : $util.dynamodb.toDynamoDBJson($util.time.nowISO8601()) | |
} | |
}, | |
"condition" : { | |
"expression" : "attribute_exists(id)" | |
}, | |
} | |
ResponseMappingTemplate: "$util.toJson($ctx.result)" | |
MutationDownvotePostResolver: | |
Type: "AWS::AppSync::Resolver" | |
DependsOn: Schema | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
TypeName: Mutation | |
FieldName: downvotePost | |
DataSourceName: !GetAtt PostDynamoDBTableDataSource.Name | |
RequestMappingTemplate: | | |
{ | |
"version" : "2017-02-28", | |
"operation" : "UpdateItem", | |
"key" : { | |
"id" : $util.dynamodb.toStringJson($ctx.args.id) | |
}, | |
"update" : { | |
"expression" : "ADD downs :plusOne, version :plusOne SET lastUpdated = :lastUpdated", | |
"expressionValues" : { | |
":plusOne" : $util.dynamodb.toNumberJson(1), | |
":lastUpdated" : $util.dynamodb.toDynamoDBJson($util.time.nowISO8601()), | |
} | |
}, | |
"condition" : { | |
"expression" : "attribute_exists(id)" | |
}, | |
} | |
ResponseMappingTemplate: "$util.toJson($ctx.result)" | |
MutationUpdatePostResolver: | |
Type: "AWS::AppSync::Resolver" | |
DependsOn: Schema | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
TypeName: Mutation | |
FieldName: updatePost | |
DataSourceName: !GetAtt PostDynamoDBTableDataSource.Name | |
RequestMappingTemplate: | | |
#set( $ddb = $util.dynamodb ) | |
{ | |
"version" : "2017-02-28", | |
"operation" : "UpdateItem", | |
"key" : { | |
"id" : $ddb.toDynamoDBJson($ctx.args.id) | |
}, | |
## Set up some space to keep track of things we're updating ** | |
#set( $expNames = {} ) | |
#set( $expValues = {} ) | |
#set( $expSet = {} ) | |
#set( $expAdd = {} ) | |
#set( $expRemove = [] ) | |
## Increment "version" by 1 ** | |
$!{expAdd.put("version", ":one")} | |
$!{expValues.put(":one", $ddb.toDynamoDB(1))} | |
## Set the "lastUpdated" timestamp ** | |
$!{expSet.put("lastUpdated", ":lastUpdated")} | |
$!{expValues.put(":lastUpdated", $ddb.toDynamoDB($util.time.nowISO8601()))} | |
## Iterate through each argument, skipping "id" and "expectedVersion" ** | |
#foreach( $entry in $util.map.copyAndRemoveAllKeys($ctx.args, ["id","expectedVersion"]).entrySet() ) | |
#if( $util.isNull($entry.value) ) | |
## If the argument is set to "null", then remove that attribute from the item in DynamoDB ** | |
#set( $discard = ${expRemove.add("#${entry.key}")} ) | |
$!{expNames.put("#${entry.key}", "${entry.key}")} | |
#else | |
## Otherwise set (or update) the attribute on the item in DynamoDB ** | |
$!{expSet.put("#${entry.key}", ":${entry.key}")} | |
$!{expNames.put("#${entry.key}", "${entry.key}")} | |
$!{expValues.put(":${entry.key}", $ddb.toDynamoDB($entry.value))} | |
#end | |
#end | |
## Start building the update expression, starting with attributes we're going to SET ** | |
#set( $expression = "" ) | |
#if( !${expSet.isEmpty()} ) | |
#set( $expression = "SET" ) | |
foreach( $entry in $expSet.entrySet() ) | |
set( $expression = "${expression} ${entry.key} = ${entry.value}" ) | |
if ( $foreach.hasNext ) | |
set( $expression = "${expression}," ) | |
end | |
end | |
end | |
## Continue building the update expression, adding attributes we're going to ADD ** | |
#if( !${expAdd.isEmpty()} ) | |
#set( $expression = "${expression} ADD" ) | |
#foreach( $entry in $expAdd.entrySet() ) | |
#set( $expression = "${expression} ${entry.key} ${entry.value}" ) | |
#if ( $foreach.hasNext ) | |
#set( $expression = "${expression}," ) | |
#end | |
#end | |
#end | |
## Continue building the update expression, adding attributes we're going to REMOVE ** | |
#if( !${expRemove.isEmpty()} ) | |
#set( $expression = "${expression} REMOVE" ) | |
#foreach( $entry in $expRemove ) | |
#set( $expression = "${expression} ${entry}" ) | |
#if ( $foreach.hasNext ) | |
#set( $expression = "${expression}," ) | |
#end | |
#end | |
#end | |
## Finally, write the update expression into the document, along with any expressionNames and expressionValues ** | |
"update" : { | |
"expression" : "${expression}", | |
#if( !${expNames.isEmpty()} ) | |
"expressionNames" : $utils.toJson($expNames), | |
#end | |
#if( !${expValues.isEmpty()} ) | |
"expressionValues" : $utils.toJson($expValues), | |
#end | |
}, | |
"condition" : { | |
"expression" : "attribute_exists(id) and version = :expectedVersion", | |
"expressionValues" : { | |
":expectedVersion" : $ddb.toDynamoDBJson($context.arguments.expectedVersion) | |
} | |
} | |
} | |
ResponseMappingTemplate: "$util.toJson($ctx.result)" | |
Schema: | |
Type: "AWS::AppSync::GraphQLSchema" | |
Properties: | |
ApiId: !GetAtt GraphQLApi.ApiId | |
Definition: | | |
type Comment { | |
author: String! | |
comment: String! | |
} | |
type Mutation { | |
addComment(id: ID!, author: String!, comment: String!): Post | |
addTag(id: ID!, tag: String!): Post | |
removeTag(id: ID!, tag: String!): Post | |
deletePost(id: ID!, expectedVersion: Int): Post | |
upvotePost(id: ID!): Post | |
downvotePost(id: ID!): Post | |
updatePost( | |
id: ID!, | |
author: String, | |
title: String, | |
content: String, | |
url: String, | |
expectedVersion: Int! | |
): Post | |
addPost( | |
author: String!, | |
title: String!, | |
content: String!, | |
url: String! | |
): Post! | |
} | |
type PaginatedPosts { | |
posts: [Post!]! | |
nextToken: String | |
} | |
type Post { | |
id: ID! | |
author: String | |
title: String | |
content: String | |
url: String | |
ups: Int! | |
downs: Int! | |
version: Int! | |
tags: [String!] | |
comments: [Comment!] | |
created: String | |
lastUpdated: String | |
} | |
type Query { | |
allPostsByTag(tag: String!, count: Int, nextToken: String): PaginatedPosts! | |
allPostsByAuthor(author: String!, count: Int, nextToken: String): PaginatedPosts! | |
allPost(count: Int, nextToken: String): PaginatedPosts! | |
getPost(id: ID!): Post | |
} | |
schema { | |
query: Query | |
mutation: Mutation | |
} | |
Outputs: | |
GraphQLApiARN: | |
Description: The App ID of the GraphQL endpoint. | |
Value: !Ref GraphQLApi | |
GraphQLApiId: | |
Description: The App ID of the GraphQL endpoint. | |
Value: !GetAtt GraphQLApi.ApiId | |
GraphQLApiEndpoint: | |
Description: The URL for the GraphQL endpoint. | |
Value: !GetAtt GraphQLApi.GraphQLUrl | |
PostDynamoDBTableDataSourceARN: | |
Description: The ARN for the Post DynamoDB table DataSource. | |
Value: !Ref PostDynamoDBTableDataSource | |
PostDynamoDBTableDataSourceName: | |
Description: The ARN for the Post DynamoDB table DataSource. | |
Value: !GetAtt PostDynamoDBTableDataSource.Name | |
I used this type of update resolver as well in my application but didn't work too.
Support DeltaSync?
I suspect the template has changed. I no longer work on AppSync, so I cannot assist further. I suggest reaching out to @dabit3 if you need to discuss updates.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
when I try, addPost just works. but updatePost throws exception , not sure what's wrong.
{
"data": {
"updatePost": null
},
"errors": [
{
"path": [
"updatePost"
],
"data": null,
"errorType": "MappingTemplate",
"errorInfo": null,
"locations": [
{
"line": 50,
"column": 3,
"sourceName": null
}
],
"message": "Encountered "" at velocity[line 80, column 2]\nWas expecting one of:\n "(" ...\n ...\n <ESCAPE_DIRECTIVE> ...\n <SET_DIRECTIVE> ...\n "##" ...\n "\\\\" ...\n "\\" ...\n ...\n "#" ...\n "#" ...\n "]]#" ...\n <STRING_LITERAL> ...\n ...\n <IF_DIRECTIVE> ...\n <ELSEIF_DIRECTIVE> ...\n <ELSE_DIRECTIVE> ...\n <INTEGER_LITERAL> ...\n <FLOATING_POINT_LITERAL> ...\n ...\n <BRACKETED_WORD> ...\n ...\n ...\n "{" ...\n "}" ...\n <EMPTY_INDEX> ...\n "
}
]
}