starpebble
Posted on April 9, 2020
Consolidate a REST API call into a GraphQL model with an AppSync HTTP resolver. Here are some instructions. Don't forget to read the summary no less than five times.
Weather Example
What is the weather in Anchorage, Alaska? Let's ask OpenWeatherMap.org in a new way with GraphQL.
# schema.graphql
type Weather {
zipCode: String!
temperature: String
}
type Query {
# getWeatherByZipCode is resolved by https://api.openweathermap.org/data/2.5/weather
getWeatherByZipCode(zipCode: String!) : Weather
}
# example graphql client query
query GetWeather {
getWeatherByZipCode(zipCode: "99504") {
zipCode
temperature
}
}
{
"data": {
"getWeatherByZipCode": {
"zipCode": "99504",
"temperature": "274.48"
}
}
}
It's cold in Anchorage, Alaska. OpenWeatherMap.org reports the temperature in Kelvin. 274.48 K degrees is chilly.
Instructions
Let's simply add exactly one AppSync HTTP resolver to an Amplify app.
- Add one GraphQL API with
amplify add api
and name it however you like - Add one
query getWeatherByZipCode
and onetype Weather
- Add one request VTL for the resolver
- Add one response VTL for the resolver
- Add one CloudFormation
AWS::AppSync::DataSource
resource - Add one CloudFormation
AWS::AppSync::Resolver
resource - Push to the cloud with
amplify push
1. Add one GraphQL API
amplify add api
2. Add one request VTL for the query getWeatherByZipCode
Add one request VTL in a file named Query.getWeatherByZipCode.req.vtl
in the resolvers directory amplify/backend/api/<apiname>/resolvers
. The VTL file will invoke the openweather.org REST API when the amplify app asks AppSync for the weather.
The request VTL template programs AppSync to get the weather from OpenWeatherMap.org with an HTTP GET.
Contents of Query.getWeatherByZipCode.req.vtl
:
#set( $appid = $util.defaultIfNull($context.args.appid, "<secretopenweathermapapikey>") )
{
"version": "2018-05-29",
"method": "GET",
"params" : {
"headers" : {
"Content-Type": "application/json"
}
},
"resourcePath": $util.toJson("/data/2.5/weather?zip=${ctx.args.zipCode}&appid=${appid}")
}
3. Add one query getWeatherByZipCode
and one type Weather
type Weather {
zipCode: String!
temperature: String
}
type Query {
getWeatherByZipCode(zipCode: String!) : Weather
}
4. Add one response VTL for the query getWeatherByZipCode
Add one response VTL in a file named Query.getWeatherByZipCode.res.vtl
in the resolvers directory amplify/backend/api/<apiname>/resolvers
.
The response VTL programs AppSync to transform the OpenWeatherMap.org REST API response into the Weather type in our GraphQL schema.
Contents of Query.getWeatherByZipCode.res.vtl
:
## 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($ctx.result.statusCode == 200)
## If response is 200, return the body.
## The response is a JSON string. Let us parse it.
#set( $json = $util.parseJson($ctx.result.body))
## Cherry pick the temperature field
$util.toJson({
"zipCode": $ctx.args.zipCode,
"temperature": $json.main.temp
})
#else
## If response is not 200, append error message to response
$utils.appendError($ctx.result.body, "$ctx.result.statusCode")
#end
5. Add one CloudFormation AWS::AppSync::DataSource
resource
Add one AWS::AppSync::DataSource
resource to the CustomResources.json template in amplify/backend/api/<apiname>/stack/CustomResources.json
"WeatherDataSource": {
"Type": "AWS::AppSync::DataSource",
"Properties": {
"ApiId": {
"Ref": "AppSyncApiId"
},
"Name": "Weather",
"Type": "HTTP",
"HttpConfig": {
"Endpoint" : "https://api.openweathermap.org"
}
}
}
6. Add one CloudFormation AWS::AppSync::Resolver
resource
Add one AWS::AppSync::Resolver
resource to the CustomResources.json template in amplify/backend/api/<apiname>/stack/CustomResources.json
The resource programs AppSync to ask the WeatherDataSource
for weather when a client invokes the getWeatherByZipCode(zipCode: String!)
query.
The FieldName
property is like glue. That's kinda why CloudFormation is like a glue gun.
"QueryOpenWeatherResolver": {
"Type": "AWS::AppSync::Resolver",
"DependsOn" : "WeatherDataSource",
"Properties": {
"ApiId": {
"Ref": "AppSyncApiId"
},
"DataSourceName": "Weather",
"TypeName": "Query",
"FieldName": "getWeatherByZipCode",
"RequestMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.getWeatherByZipCode.req.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
},
"ResponseMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.getWeatherByZipCode.res.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
}
}
}
7. Push to the cloud with amplify push
amplify push
Summary
Simplified Thinking
It's like wrangling data sources. Herd sources into exactly one spot to reduce complexity of client side code. Benefit from an increasingly consistent mental model of how an app gets data from the world. When the weather gets popular, I can simply turn on AppSync caching and make sure I'm not slowing the world down.
@http directive, throw my hands up
Just thinking out loud, maybe @http
should simply be a directive on a field, like @function
. @http
would make a REST call where @function
invokes a lambda. Amplify supports Lambda custom resolvers and omits HTTP resolvers. Don't let that stop you! It's a new way for an app to access a web service.
There are other ways to simplify. What's the best? If there is a better way to simplify, please leave a comment below.
Posted on April 9, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.