Integrating a REST API into an Amplify app's GraphQL model

starpebble

starpebble

Posted on April 9, 2020

Integrating a REST API into an Amplify app's GraphQL model

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.

  1. Add one GraphQL API with amplify add api and name it however you like
  2. Add one query getWeatherByZipCode and one type Weather
  3. Add one request VTL for the resolver
  4. Add one response VTL for the resolver
  5. Add one CloudFormation AWS::AppSync::DataSource resource
  6. Add one CloudFormation AWS::AppSync::Resolver resource
  7. 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.

💖 💪 🙅 🚩
starpebble
starpebble

Posted on April 9, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related