Table of Contents
1. Understanding The GraphQL Query Language
2. Applying a GraphQL Query to an Object Graph
3. Representing Data Models as Object Types
4. GraphQL Operations and Resolvers
5. Introspection Makes Schemas Discoverable
6. Implementing Inheritance Under the GraphQL Specification
7. A Special Addition: Support for Unions
8. Conclusion
This is Part 2 of the ProgrammableWeb API University Guide to GraphQL: Understanding, Building and Using GraphQL APIs.
In the first installment of this series we introduced you to GraphQL from a historical perspective. We looked at GraphQL in terms of the evolution of the internet, from the early days of publishing web pages under static HTML, then onto dynamically data-driven web pages and from there to desktop and mobile applications that used APIs as the primary way to work with web-based data. Finally, we talked about the arrival of GraphQL, first created by Facebook and then introduced to the worldwide programming community.
At a high level, GraphQL was created to allow web programmers working in both browser-based and native mobile formats to write queries that define exactly the data needed from an extensive graph of data in a single response. Also, whereas other APIs, particularly those that are RESTful, require executing queries over multiple trips to the network in order to create a dataset that is complete and useful to the consuming application, creating rich datasets in GraphQL can be accomplished in a single query.
GraphQL is concise and powerful. Also, its use of the object graph as its underlying data framework makes it very adaptable to the semantic web's promise of unifying all the data available on the internet in a meaningful, navigable way.
Now that we've laid the general groundwork for understanding GraphQL, it's time to take a detailed look at the details of working with the technology. We're going to cover the basics of the GraphQL query language. We'll look at the details of the root operations supported by GraphQL; query, mutation, and subscription. We'll cover GraphQL's schema discovery feature, introspection. Finally, we'll look at how GraphQL implements the spirit of object inheritance using interfaces and the extends keyword. Throughout this series and for the sake of consistency, wherever examples are needed to help make a point, we stick to the same fundamental use case involving movies, actors, and a simple graph that they could be a part of.
Understanding The GraphQL Query Language
The structure of the GraphQL query language is a declarative format that looks something like a cross between JSON and Python. The query language uses the curly bracket syntax to define a set of fields within an object (aka entity). But, unlike the way JSON uses commas to delimit a field, a GraphQL query uses line breaks or white spaces. Listing 1 below shows an example of a GraphQL query and the result of that query.
Query | Result |
---|---|
|
|
Listing 1: The GraphQL query on the right defines a result shown on the left
The meaning behind the query in Listing 1 is as follows: "Show me information about a movie according to the unique id, 6fceee97-6b03-4758-a429-2d5b6746e24e. The information to return is the movie title and release date, Also show me the directors of the movie, according to firstName, lastName, and dob. And, return the collection of actors in the movie according to the firstName, lastName and the role or roles the actor played."
The result of the query defined on the left side of Listing 1 is shown on the right side of the listing.
Understanding the Difference Between the Query and the Call
If you're already familiar with how software components call networkable APIs (also known as "API endpoints"), then you should also recognize that the query syntax shown in the listing above is not yet packaged to call the GraphQL API. It's important to understand the difference between a GraphQL query and a GraphQL API call. The query is essentially the payload that's packaged within the call. The act of calling the API delivers that payload to the API's endpoint where it is unpackaged and processed (a part of an API's workflow known as "deserialization"). From one API call to the next, that packaging is relatively the same and so, throughout this series, we will omit the packaging details in order to focus on the query payloads themselves. But to better illustrate how a query is simply a payload that's included with a call, here is what the query in listing 1 above would look like as part of a full API call if you were to hand-enter that call as a cURL command into your system's command line interface:
curl -X POST 'http://localhost:4000/' -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'DNT: 1' -H 'Origin: http://localhost:4000' -H 'authorization: ch3ddarch33s3' --data-binary '{"query":"{movie(id: \"6fceee97-6b03-4758-a429-2d5b6746e24e\"){title releaseDate directors{ firstName lastName dob}actors{ firstName lastName roles{ character}}}}"}'
cURL is not the only way to call an endpoint. It's just how it's often done by hand from a command prompt. Each programming language (Javascript, Python, PHP, Java, etc.) has it's own way of calling endpoints. In this case, the GraphQL endpoint is on the local system and so the cURL command is packaging the request and sending it to the following URI:
http://localhost:4000/
However, it could have just as easily sent it to a GraphQL API endpoint that's somewhere else on the network or Internet. One last thing to note is that with GraphQL, all API calls use the HTTP verb POST to do their bidding. This, of course, is different from RESTful APIs which, in addition to POST, may optionally make use of other HTTP verbs (GET, PUT, PATCH, etc.).
The nice thing about GraphQL is that the syntax requires no special knowledge other than understanding how to define query parameters and also how to organize the fields to display in a given data entity and its subordinates. There are no special keywords such as SELECT, FROM, GROUPBY, JOIN that you typically find in SQL. GraphQL is simply about defining the data you want from an object graph according to a parent object and its subordinates. Let's take a closer look at the details.
Applying a GraphQL Query to an Object Graph
Object graphs are similar to relational databases in that they both describe relationships between entities. However, relational databases describe entity relationships by using SQL queries to join tables of data together according to a key field while also describing the columns to display from each table. Thus, SQL queries can be quite long and complicated.
Figure 1 below shows a traditional approach to defining relationships in a relational database.
Figure 1: A traditional way to describe one-to-many relationships in a relational database
In order to determine a movie's director(s) and actors according to the tables in a relational database as shown above, you would need to write a SQL query that joins the tables, Person and MovieDirector to the Movie table. And to determine the actors in a movie we'd need to join the Person table to the MovieActor table, join the MovieActor table to the MovieRole table and then join the MovieRole table to the Movie table. That's a lot of joining.
The GraphQL query language takes a simpler approach. Take a look at Figure 2, which is an object graph that describes movies, actors, directors and roles.
Figure 2: An object graph that describes the relationships between movies, actors, roles and directors
Each entity, Director, Actor, Movie, and Role is a node. The relationship between each node, as shown in Figure 2 is an edge. (Node and edge are terms used in discrete mathematics to describe the parts of a graph.) In this case there is only one edge - has - which describes a single relationship; has.
The GraphQL we'd write to display the title, releaseDate, directors, actors and roles for each actor for a given movie, according to a unique identifier is shown below and above in Listing 1.
{
movie(id: "6fceee97-6b03-4758-a429-2d5b6746e24e"){
title
releaseDate
directors{
firstName
lastName
dob
}
actors{
firstName
lastName
roles{
character
}
}
}
}
(Please be advised using the name id for the movie identifier parameter is conventional, yet arbitrary. The unique identifier for the movie could just as easily been named, movieId. It all depends on the way that the system designer decides the pattern used to name the unique identifier of an object.)
The query shown above will return all the directors and actors from the underlying data storage technology used by the GraphQL API. Notice that the fields for each implicit director in the directors collection of a movie are defined within a set of curly brackets. Also, notice that the fields for each implicit actor in the actors collection are also defined between curly brackets. Finally, notice too that the field roles is also a collection that is part of each actor, implicitly. (Actors can play a number of roles in a single movie, as did Peter Sellers in the movie, Dr. Strangelove.) The query displays the character field for each role the actor played in the movie, again defining the field to display between the curly brackets associated with the collection.
As we've mentioned a number of times, the GraphQL query language allows you to easily declare exactly the fields for the data you want to show. Thus, in order to see the particular movie according to title and releaseDate, we write:
{
movie(id: "6fceee97-6b03-4758-a429-2d5b6746e24e"){
title
releaseDate
}
The query above will return only the title and release date for the movie according to the id, 6fceee97-6b03-4758-a429-2d5b6746e24e. (In the GraphQL query language, query parameters are defined within parentheses.) Should we want to see the title and releaseDate of all the movies on record, we'll write the following GraphQL query:
{
movies {
title
releaseDate
}
If wanted to see only the title and actors by first and last name for a particular movies, we write:
{
movie(id: "6fceee97-6b03-4758-a429-2d5b6746e24e"){
title
actors{
firstName
lastName
}
}
}
Should we want to see all the title and releaseDate for all the movies in the datastore, along with all the directors for each movie, displaying the firstName, lastName and dob of each director returned, the GraphQL query we write is:
{
movies {
title
releaseDate
Directors {
firstName
lastName
dob
}
}
}
The important thing to understand is that GraphQL allows developers to define queries according to the nodes in the underlying object graph. Also, the developer can define exactly the fields to display per particular node or collection of nodes.
Another important thing to know is that, while we used a singular and plural naming convention to distinguish an object from a collection of objects, (movie vs. movies), in GraphQL, query naming is custom and arbitrary. And, naming a query does not magically implement behavior. Query behavior must be created in the API implementation. (We'll discuss how to define and implement queries later in this article.)
In a real-world implementation of GraphQL, a developer will need to declare a query named, movie and a query named, movies. And that developer will need to define the parameters that go with each query, if any, as well as the structure of the data returned by the query. Also, the developer will need to program the behavior that the query requires. This is a lot of work, but it is not a make it up as you go along endeavor.
The GraphQL specification describes exactly how to define queries along with the data structures they return. Also, GraphQL defines the mechanism for implementing a query's behavior. This mechanism is called the resolver. A resolver is a function that gets written in the particular language of the GraphQL implementation. For example, given how the Apollo GraphQL solution is written in node.js, resolvers intended to work with an Apollo-based GraphQL API would are written in node.js as well. Figure 3 below shows the relationship between the query, movies and the resolver that implements the behavior for the query.
Figure 3: A resolver is a function the provides the behavior for a given query
Queries and resolvers, as well as mutations, subscriptions, and custom types are all part of the GraphQL type system and the root operation types, which is what we'll look at next.
Representing Data Models as Object Types
The way that data structures are defined in GraphQL is according to an object type. Object types are described using a description format special to GraphQL. The structure of the format is as follows:
type TypeName {
fieldName: fieldType
fieldName: fieldType
fieldName: fieldType
}
The following describes the various parts of the type declaration structure:
type is a GraphQL reserved word
TypeName is the name of the type. This name can be a GraphQL operation such as Query, Mutation or Subscription. Also, similar to a JSON object, the TypeName can name a custom object type, for example Actor or Movie.
fieldName is the name of a field in the object (for example id, firstName or lastName). If the containing type of the fieldName is a Query, each fieldName will describe a particular query published by the API. If the containing type is a Mutation, each fieldName will describe a mutation published by the API. If the containing type is a Subscription, each fieldName will describe the behavior for evented message transmission to external parties subscribed to the event.
In addition to supporting specific and custom type objects, the GraphQL specification supports the scalar types, String, Int, Float, Boolean and ID. ID denotes a unique identifier. Also, the specification supports arrays of scalar values and object types. For example, [String] indicates an array of the scalar value, String. [Actor] indicates an array of the custom object type, Actor. Please note that in GraphQL an array is defined by putting a type or scalar value between opening and closing square brackets.
All values in GraphQL are declared explicitly according to type. GraphQL does not support implicit type declaration. This is a key difference between GraphQL and HTTP-based RESTful APIs. Whereas RESTful HTTP APIs allow for payloads to be serialized according to formats supported by HTTP (some of which allow implicit typing), JSON is GraphQL's only supported format for payload serialization and typing is required (explicit).
Listing 2 below shows a declaration of the custom object type, Person.
type Person {
id: ID
firstName: String
lastName: String
dob: Date
knowsConnection: [Person]
likesConnection: [Person]
marriedToConnection: [Person]
divorcedFromConnection: [Person]
}
Listing 2: The type, Person is an example of the custom object type described in GraphQL's type definition format
Let's take a look at the details of the declaration of the type, Person. The type, Person publishes eight fields: id, firstName, lastName, dob, knowsConnection, likesConnection, marriedToConnection, divorcesFromConnection. The field, id is of type, ID. ID is a built-in scalar type special to GraphQL. ID is intended to describe a unique identifier. An ID is typically a string, but GraphQL expects that the string is a UUID and not human readable. GraphQL implementations such as Apollo are not obligated to auto-generate unique IDs for the ID field. That responsibility lies with the API provider.
The fields, firstName and lastName are of scalar type String. String is another one of the types built-in to GraphQL. The field, dob is of type Date. Date is a custom scalar. GraphQL allows you to define custom scalar types. Custom scalars are useful in situations in which a single value with special validation and parsing rules needs to be supported. The fields knowsConnection, likesConnection, marriedToConnection, and divorcesFromConnection are arrays of Person types, as denoted by the square brackets.
The concept of connections is one that is evolving in GraphQL. Conceptually you can think of a connection as an association between two objects in an object graph. (The term, edge is used in discrete mathematics to indicate a connection between two nodes.) A convention is developing among GraphQL developers in which a category of an edge that exists between two nodes is called a connection, with the naming convention being, categoryConnection, hence knowsConnection, indicating that the connection between two nodes is that one node knows the other.
We're going to take an in-depth look at connections as well a pagination techniques for controlling large lists associated with a connection in Part 3 of this series, How To Design, Launch, and Query a GraphQL API Using Apollo Server.
GraphQL Operations and Resolvers
Whereas a RESTful API's operations depend on the underlying protocol's verbs (eg: HTTP and its verbiage such as GET, PUT, POST, etc.), GraphQL eschews HTTP's command set and supports three root operation types; Query, Mutation, and Subscription. The sections that follow provide examples of these root operations types both in terms of declaration and execution using the GraphQL query language.
Query
A Query is, as the name implies, an operation type that has fields that describe how to get data from a GraphQL API. For those who are familiar with HTTP-based APIs, a GraphQL query most closely correlates to an HTTP GET. Listing 3, below shows an example of the implementation of a Query type in GraphQL's type definition format.
type Query {
persons: [Person]
person(id: ID!): Person
movies: [Movie]
movie(id: ID!): Movie
triples: [Triple]
triplesByPredicate (predicate: Predicate!): [Triple]
}
Listing 3: Each property in a Query type describes a query for getting from the GraphQL API.
You'll notice that in Listing 3 above there are a number of fields defined within the type, Query. Each field in the Query operation type describes a particular query supported by the API. The field persons defines a query literally named persons that returns an array of Person objects. (Again, an array is indicated by the square brackets.) The field person(id: ID!) indicates a query named person that has a parameter, id of type ID. The exclamation symbol means that a value must be provided for the parameter. The query will use the value assigned to id to do a lookup for the particular person. (Please be advised that naming the unique identifier parameter id is a matter of convention used by developers implementing GraphQL types. That the field name for unique identifier happens to be a lower-case name of similar to the GraphQL scalar type, ID is purely coincidental.)
Defining query parameters in the way shown above is part of the GraphQL specification. Later on, in Part 3 of this series. we'll take a look at how the Apollo Server implementation of GraphQL passes query parameter values onto actual query behavior. The important thing to understand now is that you declare parameters by name and type within a set of parentheses in the field definition of a particular query.
As you can see, the pattern for declaring a query that returns an array and a query that returns an object also applies to other fields in the Query definition. The query movies returns an array of Movie< objects. The query movie(id: ID!) returns a particular Movie.
However, notice that while the query, triples supports the "plural" pattern by returning an array of Triple objects, the query triplesByPredicate(predicate: Predicate!) is different for two reasons. (A Triple is custom object we created for our demonstration application that accompanies this series. Triple is not a reserved keyword in GraphQL.) First, the name of the query triplesByPredicate differs from the convention we've seen thus far. The usual pattern for query naming is plural and singular according to type; movies and movie, for example. Yet, triplesByPredicate violates this convention. This is OK because there will come a time when some queries will need to be quite specific. There is nothing in GraphQL that dictates how queries need to be named. The plural/singular pattern is conventional.
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,
Mutation | Result |
---|---|
|
|
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.
The asynchronicity built into subscriptions is one of GraphQL's superpowers
As is, REST does not include a subscription capability out of the box. Asynchronous messaging can be implemented with REST, but it's more of a bolt-on feature, usually requiring the use of a separate message publishing service such as RabbitMQ. Using both a REST service and a message publishing service invariably requires disruption to the typical API workflow. This disruption involves cutting over to a new API endpoint or even a change in technology altogether. gRPC, which is also not RESTful, is an example of another REST alternative that has subscription capability built-in. We'll discuss GraphQL subscriptions in more detail below.
Whereas RESTful APIs are intended to support stateless requests and response interactions in an asynchronous manner, GraphQL supports asynchronous interactions via subscriptions. A subscription is a mechanism by which an API client registers with a GraphQL API to receive notifications according to events generated from 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.
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
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 | |
---|---|
Query | Result |
|
|
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.
You can read more about the formal practice of using interfaces in general programming here.
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, Personable. 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 |
---|---|
|
|
Query Results | |
|
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