Topics:

GraphQL

What is GraphQL?

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. The standard was created by Facebook. Basically, you can think of it as “client side SQL”. When implementing a GraphQL API, server developers specify a GraphQL schema that defines what can be queried. Instead of the client calling REST-like urls, it generates different GraphQL queries and sends these queries to the single endpoint of the API. The server then parses the query, executes it, and returns the results back to client.

Using GraphQL

Creating and sending queries

Queries are written in GraphQL language and sent to the API in request body of a HTTP POST request with either "application/graphql" or "application/json" as Content-Type.
Queries define what type of data and what fields of the data are requested.
The API returns a result corresponding to the query in JSON format.

The following queries would request a stop with id stpt:s2980 and return its name and coordinates:

  • Using Content-Type: "application/graphql":
{
  stop(id: "stpt:s2980") {
    name
    lat
    lon
  }
}
  • Using Content-Type: "application/json"
{
  "query": "{
    stop(id: \"stpt:s2980\") {
      name
      lat
      lon
    }"
}

Example response:

{
  "data": {
    "stop": {
      "name": "Complex Studențesc",
      "lat": 45.74815,
      "lon": 21.23527
    }
  }
}

IDs

All objects in the GraphQL API have a global ID (field id), which can be used as a cache key or to refetch the object using query type node.

Global IDs in the Routing API are defined by Relay and should not be confused with other IDs (such as gtfsId) that objects may have.

Interfaces

GraphQL supports interfaces, which objects can implement by including fields required by the interface. Two interfaces used in the Routing API are Node (which has the field id used for global IDs) and PlaceInterface.

If a query type returns an interface, inline fragments have to be used to access fields defined by the object implementing the interface.

For example, query type nearest returns a list of PlaceInterfaces and types BikePark and Stop implement PlaceInterface.
The following query returns field spacesAvailable for bike parks and field code for stops.

{  
  nearest(lat: 45.74815, lon: 21.23527, maxResults: 3, maxDistance: 1500, filterByPlaceTypes: [STOP, BIKE_PARK]) {
    edges {
      node {
          place {
            lat
            lon
            ...on Stop {
              name
              gtfsId
              code
            }
            ...on BikePark {
              name
              bikeParkId
              spacesAvailable
            }
          }
          distance
      }
    }
  }
}

Variables

For more complex queries, variables can be useful.
To use variables, queries must be sent with Content-Type application/json and the query must have an operation name.

Variables are sent in a JSON object with key variables.

For example, the following query would request a route with name 550 (using Routes as an operation name):

{
  "query": "query Routes($name: String) {
             routes(name: $name) {
               gtfsId
               shortName
               longName
             }
           }",
  "variables": {
    "name":"550"
  }
}

Batching

Multiple queries can be combined and sent in one POST request. Batched queries require less server roundtrips and can be processed more efficiently on the server.

For example, the following query would request a stop with id stpt:s2980 and a route with id stpt:r886:

{
  stop(id: "stpt:s2980") {
    name
    lat
    lon
    routes {
      shortName
      longName
    }
  }
  route(id: "stpt:r886") {
    shortName
    longName
  }
}

If the request includes multiple queries with same type, they must be renamed using aliases. For example, the following query would request two routes and return them as route1 and route2:

{
  route1: route(id: "stpt:r1558") {
    shortName
    longName
  }
  route2: route(id: "stpt:r1106") {
    shortName
    longName
  }
}

Pagination

  • Query types which support pagination can be used without pagination by omitting arguments first and after, in which case all data is returned on one page

Some query types support pagination, which can be used to limit the amount of data returned per query.
Query types which support pagination return a Relay cursor connection to the data.

For example, stopsByRadius supports pagination. The following query requests stops within 300m of 45.734275, 21.219093 and returns 2 stops per page (argument first).

{
  stopsByRadius(lat: 45.734275, lon: 21.219093, radius: 300, first: 2) {
    edges {
      node {
        stop {
          name
          lat
          lon
        }
        distance
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

An example response:

{
  "data": {
    "stopsByRadius": {
      "edges": [
        {
          "node": {
            "stop": {
              "name": "Bulevardul L. Rebreanu",
              "lat": 45.73372,
              "lon": 21.21915
            },
            "distance": 64
          },
          "cursor": "c2ltcGxlLWN1cnNvcjA="
        },
        {
          "node": {
            "stop": {
              "name": "Bulevardul C. Brâncoveanu",
              "lat": 45.73299,
              "lon": 21.21949
            },
            "distance": 179
          },
          "cursor": "c2ltcGxlLWN1cnNvcjE="
        }
      ],
      "pageInfo": {
        "hasNextPage": true,
        "endCursor": "c2ltcGxlLWN1cnNvcjE="
      }
    }
  }
}

The field hasNextPage indicates whether all data has been returned or not.
If hasNextPage is true, the next page can be queried by using the value of endCursor for argument after in the query.

For example, the following query returns the next page of data:

{
  stopsByRadius(lat: 45.734275, lon: 21.219093, radius: 300, first: 2, after: "c2ltcGxlLWN1cnNvcjA=") {
    edges {
      node {
        stop {
          name
          lat
          lon
        }
        distance
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

An example response:

{
  "data": {
    "stopsByRadius": {
      "edges": [
        {
          "node": {
            "stop": {
              "name": "Bulevardul C. Brâncoveanu",
              "lat": 45.73299,
              "lon": 21.21949
            },
            "distance": 179
          }
        },
        {
          "node": {
            "stop": {
              "name": "Bulevardul C. Brâncoveanu",
              "lat": 45.73289,
              "lon": 21.21874
            },
            "distance": 205
          }
        }
      ],
      "pageInfo": {
        "hasNextPage": true,
        "endCursor": "c2ltcGxlLWN1cnNvcjI="
      }
    }
  }
}

GraphQL clients

In most cases, a GraphQL client should be used instead of plain HTTP requests, as GraphQL clients have many useful features (such as caching, batching and validating queries), which would otherwise have to be implemented manually.

Two commonly used GraphQL clients are

  • Relay by Facebook, supports React
  • Apollo, supports multiple development platforms, including Android and iOS

Further reading

Examples

The examples below send a GraphQL query using HTTP POST to https://api.opentransport.ro/routing/v1/routers/timisoara/index/graphql. This example query asks the server to find a stop with the ID stpt:s2980 and return its name, latitude and longitude coordinates, and whether is is accessible by wheelchair.
Note: If the examples provided do not return expected results, the stop id may not be in use any more and you should try again with an existing id.

Content-Type: application/graphql

Content-Type: application/json