Publish messages to SNS Topic using AppSync resolvers with HTTP datasources
This is a continuation of a series of articles where we explore how to push the limits of AppSync by shifting business logic to resolvers instead of downstream services.
There is a great article by AWS which explains how to use AppSync HTTP datasource to call AWS services https://aws.amazon.com/blogs/mobile/invoke-aws-services-directly-from-aws-appsync/, this however doesn’t apply to all services, one of which is SNS.
AWS SNS uses an old API version (version 2010–03–31) which uses XML instead of JSON: https://github.com/aws/aws-sdk-js/blob/master/apis/sns-2010-03-31.normal.json and we are going to tackle it so it works nicely as AppSync HTTP datasource.
First you need to have an SNS topic that you want to publish messages to, you can have a look to awesome AWS documentation to learn how to create one: https://docs.aws.amazon.com/sns/latest/dg/sns-tutorial-create-topic.html
Let’s create a role with a policy allowing Publish
action on your SNS Topic:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sns:Publish",
"Resource": "arn:aws:sns:us-east-1:<AWS_ACCOUNT_ID>:<SNS_TOPIC_NAME>"
}
]
}
And add AppSync to list of trusted entities on that role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "appsync.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Now with the role in place we can create a JSON config of our future datasource, let’s call it http.json
:
{
"endpoint": "https://sns.<REGION>.amazonaws.com",
"authorizationConfig": {
"authorizationType": "AWS_IAM",
"awsIamConfig": {
"signingRegion": "us-east-1",
"signingServiceName": "sns"
}
}
}
And provision a new datasource with a new role attached:
# aws appsync create-data-source --api-id <YOUR_API_ID> --name <NAME_FOR_DATA_SORUCE> --type HTTP --http-config file://http.json --service-role-arn arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAM_ROLE_NAME>
Now with a new datasource in place we can build a resolver. Let’s attach it to a simple mutation which will return a boolean true/false depending on 200 response from the SNS API:
type Mutation {
publishMessage(message: String!): Boolean
}
Let’s create a request mapping template to call SNS API to publish the message as a request.vtl
:
#set($message = $utils.urlEncode($ctx.args.message))
#set($topic = $utils.urlEncode("arn:aws:sns:us-east-1:<AWS_ACCOUNT_ID>:<SNS_TOPIC_NAME>")){
"version": "2018-05-29",
"method": "POST",
"resourcePath": "/",
"params": {
"body": "Action=Publish&Version=2010-03-31&TopicArn=$topic&Message=$message",
"headers": {
"content-type": "application/x-www-form-urlencoded"
}
}
}
Things to note here is that we are passing content-type
header as application/x-www-form-urlencoded
and message body as a query string.
Because the message can contain special characters, it is required to encode the message first using $utils.urlEncode()
built-in utility before sending it to SNS API.
The response.vtl
template will look like that:
## Raise a GraphQL field error in case of a datasource invocation error
#if($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end## If the response is not 200 then return an false. Else return true
#if($ctx.result.statusCode == 200)
true
#else
false
#end
Now let’s create a resolver using those templates and attach it to publishMessage
mutation that we have created earlier:
# aws appsync create-resolver --api-id <YOUR_API_ID> --type-name Mutation --field-name publishMessage --data-source-name <HTTP_DATA_SOURCE> --request-mapping-template file://request.json --response-mapping-template file://response.json
Now you can test the mutation:
mutation publishMessage {
publishMessage(message:"Sending message to SNS Topic directly from AppSync")
}
The response will be as below and message will be published to SNS topic:
{
"data": {
"publishMessage": true
}
}
That’s it, we saved one more AWS Lambda function for the world!
Clap if you found that article series useful and happy coding!