Skip to main content

GraphQL Overview

GraphQL is an API technology developed by Facebook in 2015 and adopted over the world by top technological companies. In this guide we will show the benefits of GraphQL as a technology and also provide an overview of some powerful and advanced features of GraphQL query syntax. After this article you will understand what benefits GraphQL gives you as a customer as well as be able to use advanced features of GraphQL to power your queries.

GraphQL Technology Overview#

GraphQL is an API technology which allows allows customers to query API in the requests. Contrary to REST where specific endpoints are used to identify the resource, the GraphQL exposes a single endpoint with a global GraphQL schema. GraphQL schema defines what is possible to query and underlying data types. Customers are sending queries against the schema to the GraphQL server as a POST request. You can query whatever you want using a single API endpoint (but according to the granted permissions) and the shape of the response will match the shape of the query.

For Spire, as data provider, the GraphQL gives following benefits:

  • Integrated API - in one query you can query multiple data sources at once. For example, querying for the vessel you can get vessel characteristics, destination port, predicted route and so on. Such kind of API is impossible to create with REST approach.
  • Single API endpoint - single endpoint api.spire.com/graphql where all the operations are available. Integrating any new feature into GraphQL should be straightforward for Spire as well as for our customers.
  • Flexibility - in the long term GraphQL allows more flexibility than REST. At first, GraphQL supports queries, mutations, subscriptions out of the box, where subscriptions are really promising for the Maritime domain. Second, GraphQL schema could be evolved in a way to integrate various data sources.
  • Documentation - GraphQL schema serves as live documentation for our API.

For you, as a customer, GraphQL gives a following benefits:

  • Query only what you need - as a customer you can focus only on a subset of the data you need therefore reducing the amount of transferred data which will lead to better performance.
  • Parallel queries - you might sent a query with a multiple root fields which will be resolved in parallel.
  • Typesafe queries - all queries are validated against GraphQL schema and safe to execute.
  • API Playground - at api.spire.com/graphql there is a GraphQL playground available where you can play and prototype query of any complexity.

GraphQL represents a massive leap forward for API development. Type safety, introspection, generated documentation, and predictable responses benefit both the data provider and customers of the API platform.

GraphQL Syntax Overview#

Named and Unnamed Queries#

The basic GraphQL query to get mmsi and name for first 3 vessels might look like:

{
vessels(first: 3) {
nodes {
staticData {
mmsi
name
}
}
}
}

This is so-called unnamed query because query itself does not have his own name. Let's compare with named query:

query myVessels {
vessels(first: 3) {
nodes {
staticData {
mmsi
name
}
}
}
}

Named queries are more generic just because they can have a variables and variables can be passed to arguments:

query myVessels($numOfVessels: Int!) {
vessels(first: $numOfVessels) {
nodes {
staticData {
mmsi
name
}
}
}
}

To execute the query you need to provide query variables alongside with query as json:

{
"numOfVessels": 10
}

On the network level named query with arguments are represented as extended version of POST request:

POST https://api.spire.com/graphql
{
"operationName": "myVessels"
"query": "
query myVessels($numOfVessels: Int!) {
...
}
",
"variables": {
"numOfVessels": 10
}
}

The more realistically looking example of the query with variables:

query queryVesselsPositions(
$limit: Int
$after: String
$startTime: DateTime!
$endTime: DateTime!
) {
vessels(
first: $limit
after: $after
lastPositionUpdate: { startTime: $startTime, endTime: $endTime }
) {
pageInfo {
endCursor
hasNextPage
}
nodes {
id
staticData {
name
mmsi
imo
}
lastPositionUpdate {
timestamp
updateTimestamp
latitude
longitude
heading
speed
rot
accuracy
course
maneuver
navigationalStatus
collectionType
}
}
}
}

Renaming#

It's possible to change the name of the field in GraphQL. You can rename any field, at any level of the query. The syntax for renaming is always newName: fieldName for any field.

query queryVessels {
vessels(mmsi: [244234000]) {
nodes {
staticData {
imo
mmsi
call_sign: callsign
ship_type: shipType
}
}
}
}

The response will look like:

{
"data": {
"vessels": {
"nodes": [
{
"staticData": {
"imo": 9361354,
"mmsi": 244234000,
"call_sign": null,
"ship_type": null
}
}
]
}
}
}

You can change the name of the field in the query, but you can't change structure of the query. For example mmsi will always be child of staticData.

Multiple Root Fields#

One query can contain multiple root fields. We need to use the renaming feature of GraphQL syntax to query multiple vessels fields. The two root queries tankers and cargo will be executed in parallel, but response will come when both of them are resolved.

{
tankers: vessels(shipType: [TANKER_CRUDE, TANKER_PRODUCT, TANKER_CHEMICALS]) {
nodes {
staticData {
mmsi
name
}
}
}
cargo: vessels(shipType: [CONTAINER, GENERAL_CARGO]) {
nodes {
staticData {
mmsi
name
}
}
}
}

The response will look like:

{
"data": {
"tankers": { ... },
"cargo": { ... }
}
}

Notes:

  • Each root query is resolved in parallel, but the entire result will come when all root queries will be resolved.
  • If you query multiple root queries, you might need to deal with multiple pagination cursors. Each sub query will have an independent pagination cursor.
  • Each root query counted once for rate limiter.

Fragments#

Fragment is a reusable piece of GraphQL query. For example, for some vessels you are interested in last position, but for another vessels you are interested in the current route:

query {
vesselsWithPosition: vessels(mmsi: [244234000, 413801932, 259605000]) {
nodes {
id
staticData {
imo
mmsi
callsign
shipType
}
lastPositionUpdate {
collectionType
latitude
longitude
}
}
}
vesselsWithVoyage: vessels(mmsi: [412338057, 338164033, 710127102]) {
nodes {
id
staticData {
imo
mmsi
callsign
shipType
}
currentVoyage {
destination
eta
}
}
}
}

You can see that staticData part is duplicated across the queries and we can extract it to the fragment and reuse:

query {
vesselsWithPosition: vessels(mmsi: [244234000, 413801932, 259605000]) {
nodes {
id
staticData {
...vesselStaticData
}
lastPositionUpdate {
collectionType
latitude
longitude
}
}
}
vesselsWithVoyage: vessels(mmsi: [412338057, 338164033, 710127102]) {
nodes {
id
staticData {
...vesselStaticData
}
currentVoyage {
destination
eta
}
}
}
}
# NOTE: fragment declared outside of the query brackets query { ... }
# Declare fragment on type
fragment vesselStaticData on VesselStaticData {
imo
mmsi
callsign
shipType
}

Notes:

  • Fragment must be used within the query, otherwise GRAPHQL_VALIDATION_FAILED error will be returned.
  • You can have several fragments on one type and use them in the same time

Unions#

Usually when you query a field it has a single possible type. GraphQL unions are used to represent scenario scenarios when there are several possible return types for the same field.

For example, when you query for a predicted vessel route, depending on origin argument, the route could start from a point or from a port. In the query only one of this branches will have a result depending how origin is specified:

  • If the origin is provided as unlocode then it will resolve to ... on Port branch.
  • If the origin is provided as coordinates then it will resolve to ... on GeoPoint branch.
{
predictedVesselRoute(
vessel: { mmsi: 205283000 }
origin: { unlocode: "TTPTS" }
destination: { unlocode: "COBAQ" }
) {
journey {
origin {
# if origin is geo point (NO)
... on GeoPoint {
latitude
longitude
}
# if origin is port (YES)
... on Port {
name
unlocode
centerPoint {
latitude
longitude
}
}
}
}
}
}

Will result to:

{
"data": {
"predictedVesselRoute": {
"journey": {
"origin": {
"name": "Point Lisas Ports",
"unlocode": "TTPTS",
"centerPoint": {
"latitude": 10.400300025939941,
"longitude": -61.49700164794922
}
}
}
}
}
}