blog

GraphQL Logo

GraphQL Requests – Behind the Scenes with the Apollo iOS Client

by

Image of GraphQL Logo

In my last post I took a look at using the Apollo iOS GraphQL client framework to access a GraphQL backend running on the Graphcool GraphQL mBaaS. Shortly afterwards Brandur Leach, an API engineer at Stripe posted "Is GraphQL the Next Frontier for Web APIs?". In his post Brandur gives a good overview of the current API development space, compares GraphQL to other technologies, and ultimately puts his support behind GraphQL. The follow-on discussion on Hacker News is a bit mixed, with some comments in support of GraphQL along with a few dismissing it. Some advocate support for both REST-like and GraphQL APIs, given that with a sensibly designed backend, support for both is possible with too much additional work. Stripe has a popular REST API that is used by a lot of developers, given Brandur’s opinions, it will be interesting to see if they take this hybrid approach and start offering a GraphQL interface as well.

Regardless of whether GraphQL will gain more traction compared to other approaches or not, I wanted to dive a bit deeper into the client side of things and get a better understanding of how the Apollo iOS framework and apollo-codegen tool work.

GraphQL Queries Under the Hood

The AllAirports.graphql query I created and fed into the apollo-codegen tool results in the AllAirportsQuery class in the API.swift file. In the sample app we use this query on line 19 of AirportFetcher.swift as a parameter to the fetch() method of the ApolloClient class to query the Graphcool backend.

Granted it’s a basic query, but the body of the HTTP request created by Apollo iOS simply consists of some JSON which looks like:

{
  "query": "query AllAirports {  allAirports {    __typename    airportId    identifier    airportType    name    elevation    isoCountry    isoRegion    municipality    gpsCode    iataCode    localCode    homepageURL    wikipediaURL    scheduledService    latitude    longitude  }}"
}

When the fetch() method is called a query is made to the GraphQL server or results are returned from the local cache depending on the contents of the cache and the current cache policy. For now, we’ll just consider the case where results are actually returned from the GraphQL server and will worry about cached results in a future post.

Queries Over HTTP

When creating an ApolloClient instance using the convenience initializer on line 15 of AirportFetcher.swift the ApolloClient class sets its networkTransport variable to an instance of the HTTPNetworkTransport class. This class implements the NetworkTransport protocol, which defines a single method named send(…), that should send a GraphQL operation to a server.

The HTTPNetworkTransport class implements the send(…) method using the URLSession APIs to create an URLSessionDataTask which makes a POST request containing the above JSON. In its completion handler it deals with errors and in the case of a valid response, tries to cache the response, and then calls the trailing closure passed to fetch(…) with the parsed results. Next let’s take a quick look at what a valid response looks like.

JSON Responses, Without REST

In response to the all airports query, the raw response is some more JSON, truncated here for brevity:

{
  "data": {
    "allAirports": [
      {
        "name": "Sealand Helipad",
        "latitude": 51.894444,
        "iataCode": "",
        "identifier": "ZZ-0001",
        "isoCountry": "GB",
        "__typename": "Airport",
        "scheduledService": true,
        "isoRegion": "GB-ENG",
        "airportType": "heliport",
        "longitude": 1.4825,
        "airportId": 46378,
        "elevation": 40,
        "wikipediaURL": "https://en.wikipedia.org/wiki/Principality_of_Sealand",
        "gpsCode": "",
        "municipality": "Sealand",
        "localCode": "",
        "homepageURL": "http://www.sealandgov.org/"
      },
      {
        "name": "Glorioso Islands Airstrip",
        "latitude": -11.584277777799999,
        "iataCode": "",
        "identifier": "ZZ-002",
        "isoCountry": "TF",
        "__typename": "Airport",
        "scheduledService": true,
        "isoRegion": "TF-U-A",
        "airportType": "small_airport",
        "longitude": 47.2963888889,
        "airportId": 307326,
        "elevation": 11,
        "wikipediaURL": "",
        "gpsCode": "",
        "municipality": "Grande Glorieuse",
        "localCode": "",
        "homepageURL": ""
      }
    ]
  }
}

The response JSON doesn’t look much different here than what you might expect from a REST-like API, but if you look closely you’ll see that each response includes a "__typename" attribute reflecting that each result here is an "Airport" model type from the schema we created in the Graphcool console.

The Apollo iOS framework basically takes this response, parses the JSON, and validates the data as it constructs the AllAirport instances passed to the trailing closure of the fetch() method. The AllAirport struct is defined in API.swift and is created by the apollo-codegen tool. This approach of query specific generated native types means that the results of a query only contain what you requested in the query. This is a nice approach to reducing errors. Too often when mapping the responses of REST-like APIs to native objects you end up with an incomplete local state as either the returned JSON doesn’t define all the attribute values or local objects have additional attributes that need to be initialized separately. I’m curious to see if other GraphQL client libraries take a similar approach, or if they leave the model definition and response mapping up to the developer.

Wrapping Up

From this quick post we can see that the underpinnings of a GraphQL client-server exchange aren’t radically different than with a REST-like API. For those considering a hybrid REST / GraphQL approach discussed above this should be reassuring, and perhaps the claims of dual support without too much additional effort aren’t far off. In my next post I’ll take a closer look at how the response JSON is parsed, converted to the generated types, and cached by the Apollo iOS client.

+ more

Accurate Timing

Accurate Timing

In many tasks we need to do something at given intervals of time. The most obvious ways may not give you the best results. Time? Meh. The most basic tasks that don't have what you might call CPU-scale time requirements can be handled with the usual language and...

read more