Back to blog
Aug 12, 2024
3 min read

AWS AppSync (GraphQL)

GraphQL API

As part of my pltfrm work, I needed to build a GraphQL API, using AppSync.

AppSync looks … well, just odd. It seems to start off with some good ideas and then get bogged down in detail trying to solve new problems it has introduced.

Schema

At it’s heart is a Schema : for my purposes, this is my schema.

type Query {
    getWeather(timestamp: String!): WeatherReport
    getHourlyWeather(timestamp: String!): HourlyWeatherReport
    getInsult(id: Int): InsultData
}

type InsultData {
    insultId: Int
    insult: String
}

type IntervalMeasurement {
    timestamp: String!
    measurement: Float!
}

type WeatherReport {
    FeelsLike: Float
    Temperature: Float
    Clouds: Float
    Uvi: Float
    WindSpeed: Float
    DewPoint: Float
    Pressure: Int
    Weather: String
    Humidity: Int
    WindDeg: Int
    Visibility: Int
    Rain: [IntervalMeasurement!]!
}

type HourlyForecast {
    clouds: Float
    dewPoint: Float
    dt: Int
    feelsLike: Float
    humidity: Int
    pop: Int
    pressure: Int

    temp: Float
    uvi: Float
    visibility: Int

    weather: [WeatherDescription!]!

    windDeg: Float
    windGust: Float
    windSpeed: Float
}

type WeatherDescription {
    id: Int
    main: String
    description: String
    icon: String
}

type HourlyWeatherReport {
    actualTime: String
    forecastHours: [HourlyForecast!]
}

schema {
    query: Query
}

That all looks reasonable sensible. I’m redefining my weather model, and for fun I added a stream of random insults - I asked ChatGPT for a list of 100 insults in JSON, and spent the next 10 minutes chuckling away.

Resolvers

You then get to define your resolvers - defined in Data Sources. My two weather queries come from two DynamoDB tables respectively, so the query can specify the PK of the table - a timestamp and then the resolver is pretty straight forward.

resource "aws_appsync_resolver" "get_weather" {

  api_id = aws_appsync_graphql_api.this.id
  type   = "Query"
  field  = "getWeather"

  data_source = aws_appsync_datasource.weather_datasource.name

  request_template = <<EOF
  {
    "version": "2017-02-28",
    "operation": "GetItem",
    "key": {
      "timestamp": $util.dynamodb.toDynamoDBJson($ctx.args.timestamp)
    }
  }
  EOF

  response_template = <<EOF
  #if($ctx.error)
    $util.error($ctx.error.message, $ctx.error.type)
  #else
    $util.toJson($ctx.result)
  #end
  EOF
}

I added the third to work out how to integrate a Lambda, and it is pretty straightforward once you have the permissions sorted : just returning a Map to match the schema.

public Map<String, Object> handleRequest(final Map<String, Object> stringObjectMap, final Context context) {

  final Map<String, Object> response = new HashMap<>();
  response.put("insultId", insultIndex);
  response.put("insult", this.insults.get(insultIndex));

  return response;
}

Once I have an API key, that all works.

Weirdness # 1 : front end only ?

It really doesn’t look like they want you to use this from the back-end. I guess the main purpose of GraphQL is to make mobile and web applications faster by only sending the data you need; but I haven’t yet found a nice library for Java to try this from the back end.

Weirdness # 2 Authorisation and protection

For a REST API, you can specify e.g. scope for each endpoint, and control access. The ‘open’ nature of GraphQL makes that harder; as does the fact that the schema can reference itself, so that you could get deep loops of data.

This is a well known issue with GraphQL : they even say they’re avoiding talking about it

¯\_(ツ)_/¯