GraphQL APIs for Everyone: An In-Depth Tutorial on How GraphQL Works and Why It's Special

Continued from page 1.

The second difference to notice about triplesByPredicate(predicate: Predicate!) is that it has a query parameter that is not a unique identifier. In fact, the parameter, predicate, which is required as indicated by the exclamation symbol, is of type Predicate. Predicate is one of our custom enumeration types particular to our demonstration application. (GraphQL does support custom enumerations.) The enumeration, Predicate, is defined as follows:

enum Predicate {
  KNOWS
  LIKES
  WORKED_WITH
  MARRIED_TO
  DIVORCED_FROM
}

Thus, the query triplesByPredicate(predicate: Predicate!) translates into "Show me all triples according to the Predicate enumeration value passed to the query parameter, predicate."

The type Query is a root operation that is described in the GraphQL specification as the way by which users retrieve data from a GraphQL API. The Query type provides a good deal of flexibility allowing users to define how data is structured in a response from a GraphQL API. Yet, reading data by way of a query is only one aspect of working with a GraphQL API. We also need to add, update and delete data. These activities are done using the root operation type, Mutation.

Mutation

A Mutation operation type describes how to add, update or delete data in the API. As the name implies, a Mutation describes how to mutate data in the API. The HTTP corollaries to Mutation are POST, PUT, PATCH and DELETE. Listing 4 below shows a Mutation type that has methods for adding and updating an object type Movie, adding a Person and adding a Triple. (Movie, Person and Triple are custom object types. The exclamation character, !, indicates a required parameter.)

type Mutation {
   addMovie(movie: MovieInput!): Movie
   updateMovie(movie: KnownMovieInput): Movie
   addTriple(triple: TripleInput): Triple
   addPerson(person: PersonInput): Person
}

Listing 4: A Mutation names the various behaviors for adding, updating and deleting data from the API

Listing 5, below shows an example of syntax for executing the mutation, addPerson along with the results on the left side of the listing,

MutationResult
mutation{
  addPerson(person: {firstName: "Marlon",                                 
                     lastName: "Brando",
                     dob: "1924-04-03"})
  {
    id
    firstName
    lastName
    dob
  }
}
{
  "data": {
    "addPerson": {
      "id": "4ddac860-0769-42e3-9dd5-3901fbe33a11",
      "firstName": "Marlon",
      "lastName": "Brando",
      "dob": "1924-04-03"
    }
  }
}

Listing 5: The result of executing the mutation, addPerson()

The important thing to understand about mutations is how they are intended to be used with custom object types that also are defined within the type system. Thus the mutation addMovie, as shown above in Listing 5 above, is intended to add the data contained in the type, MovieInput to the API's datastore. An Input type is an abstraction particular to GraphQL that denotes a type that is to be used with a mutation to input data. An Input type aggregates data for input into a single object. 

Just as a behavior for a query is implemented in a corresponding resolver, so too is behavior for a particular Mutation. Developers will define a particular mutation and its response type as a field in the Mutation root operation type. Then, the behavior for that mutation will be implemented in a corresponding resolver. (We'll cover the Input type and mutation resolvers in detail in Part 3 of this series)

Subscription

A Subscription is an operation type that is required when supporting the PubSub pattern within a GraphQL API. A GraphQL API can publish one or many events available for subscription. This is useful in an event-driven real-time scenario when data is constantly being updated. An example use case might be a stock ticker where an end user wants to monitor, in real-time, price changes to a public stock.

Each field in a Subscription describes a particular event generator that corresponds to an event publication. Listing 6 below, shows a description of the onPersonAdded event generator.

type Subscription {
    onPersonAdded(channelName: String): Event
}

Listing 6: GraphQL supports publishing events to which listeners can subscribe

The event generator gets fired internally in the GraphQL API server each time a new Person is added using the API's addPerson mutation. The details of firing a message according to an event is particular to the given programming frame used to implement the GraphQL implementation. In Part 3 of this series we'll cover the details about implementing subscriptions using Apollo Server.

For now, the important thing to understand is that users of the API will submit a subscription declaration in the GraphQL query language to register to an event, such as onPersonAdded(). Once registered, the user will get messages from the API server when the event has been fired internally in the API.

Figure 4 below describes the way a client registers a custom subscription to receive event messages. In this case, the event of interest is named, onPersonAdded. The example API is programmed to fire onPersonAdded whenever a new person is added to the API's datastore.

Figure 4: Once a client registers a subscription, it will receive messages from the API when a predefined event(s) occur

Figure 4: Once a client registers a subscription, it will receive messages from the API when a predefined event(s) occur.

The specifics of working with a subscription is as follows. (In this case, in Figure 3 above, the client is using, the browser-based Apollo Server GraphQL Playground.) (1) A GraphQL subscription statement is sent to the API. The subscription statement creates a connection to the API server and listens for messages associated with the event, onPersonAdded. (2) In a separate browser window, a custom mutation, addPerson is executed. The mutation addPerson has been independently programmed on the server-side to fire an onPersonAdded event and send a message when a person is successfully added to the API's datastore. (3) The first browser window which is listening for onPersonAdded messages synchronously receives the message that was generated when the person was added.

Adding continuous communication between clients and the server using subscriptions adds a new dimension to working with an API. Regardless of the API's underlying architectural style, the chief benefit of subscriptions is efficiency. Instead of the client constantly and very inefficiently polling the server to check for data updates, the server just sends updated data as soon as it's available. Operationally, subscriptions make it possible for applications such as Facebook to implement real-time updates of comments and messages; a great feature that you'll probably recognize if you're a user of Facebook.

As mentioned previously a Subscription is a root operation type that is part of the GraphQL specification. Thus, developers can create none, one or many subscription events within a given API. Once subscription events are defined, a developer will fire those events in the corresponding resolver methods.

Event publishing can be a confusing subject, especially for those developers that are typically accustomed to writing an API that uses only synchronous request/response interactions. It takes time to fully absorb the concepts and practices of the event-driven programming paradigm. The important thing to understand right now is that GraphQL supports asynchronous event publishing by way of subscriptions. (We're going to take a more detailed look at subscription in Part 3.)

Introspection Makes Schemas Discoverable

One of the great benefits of GraphQL is that it provides a feature called introspection that makes it easy to discover the structure of a schema of an API at runtime. Technologies implementing GraphQL can use introspection to generate documentation for use by humans, as shown below in Figure 5.

Figure 5: GraphQL introspection make it easy to generate API documentation out of the box

Figure 5: GraphQL introspection make it easy to generate API documentation out of the box

Proponents of GraphQL will likely argue that this is another advantage over REST. Whereas RESTful APIs primarily rely on a completely separate description (eg: an OpenAPI description) for automation of documentation or generation of SDKs, GraphQL APIs have this capability built-in to them. Also, very much like OpenAPI-based descriptions of RESTful APIs, introspection makes machine readable discovery possible. For example, all one machine needs to do to discover the details of a GraphQL API of interest is to configure and execute a __schema query, as shown in the left panel below in Listing 7. As within any query executed in GraphQL, you can define the fields that you want to return in the query result. When it comes to running a __schema query for types, the list of fields you can query is quite large, and each item in the list of fields can have its own list of fields. (The type object publishes the fields: kind, name, description, fields, interfaces, possibleTypes, enumValues, inputFields, ofType.) Thus, the result of all data available in the entirety of a running the query __schema can go on for pages.

For the purpose of concise demonstration, Listing 7 below shows the types that exist in this article's demonstration application, displaying only the field name of the type. The query is in the left side of Listing 7. The result of running the introspection query is in the right side of the listing.

Query all Person objects in the system
QueryResult
{
  __schema {
    types {
      name
    }
  }
}
#A partial display of introspection output
{
  "data": {
    "__schema": {
      "types": [
        {
          "name": "Query"
        },
        {
          "name": "CursorPaginationInput"
        },
        {
          "name": "String"
        },
        {
          "name": "Int"
        },
        {
          "name": "Persons"
        },
        {
          "name": "Person"
        },
        {
          "name": "Personable"
        },
        {
          "name": "ID"
        },
        {
          "name": "Date"
        },
        {
          "name": "PersonConnection"
        }
.
.
.
}

Listing 7: A partial display of introspection output when running a __schema query for all the types in the API showing only the name field.

Being able to determine the characteristics of a GraphQL API at runtime to a fine grain opens up a host of possibilities, particularly as machine-to-machine interactions, the Internet of Things (IoT) and AI-powered bots continue to proliferate on the internet.

Implementing Inheritance Under the GraphQL Specification

The GraphQL specification also describes two ways to support inheritance similar to those found in object-oriented programming languages such as Java and C#. One way is to use an interface to describe fields that a type must implement. The other way it use the keyword, extend, is to add more fields in an existing type.

Let's take a look at the details.

Support for Interfaces

In addition to object types GraphQL also supports a certain degree of inheritance by specifying the keyword, interface. Interfaces are common in object-oriented programming. An interface is a named abstraction that contains properties and methods. Once an interface, along with its properties and methods has been defined, those properties and methods must be supported by any new object that is initialized as an implementation of that interface.

When it comes to GraphQL, object types implementing a particular interface need to define the fields declared in the interface. Having to redefine fields in an object type that are already defined in the interface might seem like redundant work. But, such redefinition is a common task in any programming language that supports interfaces. The difference with GraphQL is that, whereas in an object-oriented programming language such as Java an interface will define methods that need to be provided by classes implementing the interface, in GraphQL object types do not define methods directly. (Remember, implementing the behavior of an object type is done in a resolver.) Rather in GraphQL, a type that implements an interface must define the fields defined in the particular interface. The important thing to understand is that in traditional object-oriented programming languages, interface implementation is about providing behavior in methods. In GraphQL, interface implementation is about naming fields.

Listing 8 below shows how the interface, Personable is implemented in the object types, Person and Actor.

interface Personable {
        id: ID
        firstName: String
        lastName: String
        dob: Date
}
 
   type Person implements Personable{
        id: ID
        firstName: String
        lastName: String
        dob: Date
    }
    
    type Actor implements Personable{
        id: ID
        firstName: String
        lastName: String
        dob: Date
        roles: [Role]
    }

Listing 8: The Person and Actor object types implement the interface, Personable

Notice that both the types, Person and Actor implement the fields id, firstName, lastName, and dob as is required by the fact of using the interface, Person. A type implementing an interface must implement all the fields in the interface. However, notice too that while the type, Actor implements all the fields defined by the interface, Personable, Actor also provides a field, roles: [Role]. Adding fields to a type that implements an interface is permissible. However, the field, roles is only queryable against the type Actor, not the interface Personable, as you'll see in the next paragraph.

Implementing an interface in GraphQL can seem as if you are doing a redundant work but it's worth it because you can you can query GraphQL according to an interface. Listing 9, below, shows how to write a query in GraphQL that returns information according the fields defined in an interface. In this case we're running the movies query, but retrieving data according to the fields in the interface, Film. The ellipsis (...) indicates that the query is using a GraphQL inline fragment.

 type Person implements Personable{
      id: ID
      firstName: String
      lastName: String
      dob: Date
  }

  extend type Person {
      marriedToConnection: [Person]
      divorcedFromConnection: [Person]
      knowsConnection(paginationSpec: CursorPaginationInput): PersonConnection
      likesConnection(paginationSpec: CursorPaginationInput): PersonConnection    
  }

Listing 9: Creating a query according to an interface

Using extend to Implement Inheritance

GraphQL does specify a way to add fields onto an existing type. You do this using the keyword, extend. Listing 10 below shows an example of using extend to add fields to the existing type, Person. Whereas early on in this article we defined Person as a standalone type, Listing 10 shows the type Person as being an implementation of the interface Personable that's extended to include additional fields using the keyword, extend.

type Person implements Personable{
        id: ID
        firstName: String
        lastName: String
        dob: Date
    }

    extend type Person {
        marriedToConnection: [Person]
        divorcedFromConnection: [Person]
        knowsConnection(paginationSpec: CursorPaginationInput): PersonConnection
        likesConnection(paginationSpec: CursorPaginationInput): PersonConnection    
    }

Listing 10: The keyword, extend allows developers to add fields to an existing type

Notice that the type, Person has been extended to support the fields, marriedToConnection, divorcesFromConnection, knowsConnection, and likeConnection. These additions do not affect the queries made against the type. The type will present itself with all fields regardless of how the fields were defined. In addition, be advised that once the keyword extend is applied to a type, that type is modified globally.

A Special Addition: Support for Unions

A union is another GraphQL abstraction that provides a way to return data according to a variety of concrete types. Unlike interfaces that can be used to describe common fields between types, a union provides a way to create a GraphQL query that returns concrete types that have different field definitions.

Unions are declared in a type definition, as shown Listing 11 below. The meaning of the example is, define a queryable type, SearchResultObject that will return back data that is in either the Person object type or the Actor object type.

union SearchResultObject = Person | Actor

Listing 11: A union is a type that combines object types with differing fields.

Creating a query according to a union is a bit tricky in that you need to use inline fragments to get a desired result. For example, the union SearchResultObject as shown above in Listing 11 will return fields that are in both the types Person or Actor. The type Person has a field marriedTo while the type Actor has a field roles which is a array of Role objects. (A Role object describes the movie and a character in that movie.)

Listing 12, below shows the objects in play for utilizing a union, the query required to get the data according to the union and the results of the query.

Server side type definitions (Comments in tripe quotes)Client side query in GraphQL
"""
Create a search result object that returns fields either in the Person type or Actor type
"""
   union SearchResultObject = Person | Actor

   type Person implements Personable{
        id: ID
        firstName: String
        lastName: String
        dob: Date
        marriedTo: Person
    }
    
    type Actor implements Personable{
        id: ID
        firstName: String
        lastName: String
        dob: Date
        roles: [Role]
    }

   type Role {
     character: String!
     movie: Movie
   }

"""
Define a query on the server side type definition that returns an array containing either Actor or Person types according to last name.
"""
type Query {
 getPersonActor(lastName: String!): [PersonActorSearch]
}
{
  getPersonActor(lastName: "Bowie") {
    ... on Person {
      firstName
      lastName
      marriedTo {
        firstName
        lastName
       }
    }
    ... on Actor {
      firstName
      lastName
      roles{
        character
        movie {
          title
        }
      } 
    }
  }
}
Query Results
{
  "data": {
    "getPersonActor": [
      {
        "firstName": "David",
        "lastName": "Bowie",
        "roles": [
          {
            "character": "Thomas Jerome Newton",
            "movie": {
              "title": "The Man Who Fell to Earth"
            }
          }
        ]
      },
      {
        "firstName": "Donnie",
        "lastName": "Bowie"
        "marriedTo: {
          "firstName": "Emilia",
          "lastName": "Bowie"
        }
      },
      {
        "firstName": "Emilia",
        "lastName": "Bowie"
        "marriedTo: {
          "firstName": "Donnie",
          "lastName": "Bowie"
        }
      }
    ]
  }
}

Listing 12: Defining and executing a query based on a union

Listing 12, above shows only the logic executed on the query side to get the desired data. There is still a good deal of work that needs to be done to create a server-side resolver that actually implements the behavior that returns the expected data according to the query and union specification. This is complex work that requires an advanced understanding of programming logic in general and GraphQL in particular. The important things to understand on an introductory basis is that a union provides a way to query data according to fields that are not common between object types.

Conclusion

In this article we introduced you to the fundamentals of GraphQL as described in the GraphQL specification. We looked at the basics of defining a GraphQL type. We looked at the root operation types, Query, Mutation, and Subscription. We discussed the relationship between operation types and resolvers. I showed you how introspection makes it possible for any human or machine to learn the exact details of a GraphQL API by running queries against the __schema object. And, I showed how GraphQL supports the rudimentary principles of inheritance using interface, extend and union.

Now that you have a basic understanding of the fundamental concepts and basic components described in the GraphQL specification, it's time to make an operational API in GraphQL. The next installment in this series will show you how to implement a fully operational GraphQL API using Apollo Server. Apollo Server is a popular GraphQL API framework written under Node.js. In addition to implementing the movie industry types we described in this installment, we'll also provide data for the types and implement query, mutation and subscription behavior for those types. And, we'll do it in a hands-on manner by providing a sample application you can use to follow along.

Getting the concepts counts. But working directly with fully operational code provides a deeper level of learning that will make your understanding of GraphQL much more meaningful.

Next: Part 3 -- Hands-On: How To Design, Launch, and Query a GraphQL API Using Apollo Server

Be sure to read the next GraphQL article: Hands-On: How To Design, Launch, and Query a GraphQL API Using Apollo Server

 

Comments (0)