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

Introduction to Designing, Launching and Querying a GraphQL API

This is Part 3 of the ProgrammableWeb API University Guide to GraphQL: Understanding, Building and Using GraphQL APIs

In the previous two installments of this series (see Part 1: What is GraphQL and How Did It Evolve From REST and Other API Technologies and Part 2: GraphQL APIs for Everyone: An In-Depth Tutorial on How GraphQL Works and Why It's Special), we talked about how GraphQL emerged from the IT landscape. We also provided an overview of how to use the GraphQL query language against a GraphQL API. For you API providers out there, now it's time to get down to the nitty-gritty and show you the work that goes into creating and providing a fully functional GraphQL API. In this installment, we're going to examine not only how to define the data types for the API, but also how to program the behaviors that allow your users to get, add, update and delete data. In addition, you'll learn how to put a layer of security authentication on the API as well as how to construct asynchronous connections in GraphQL using subscriptions. Finally, well cover pagination over large datasets and how to create and work with GraphQL directives to decorate a GraphQL query for special behavior.

Before we move into the concept and programming behind creating a GraphQL API, let's answer a fundamental question, why Apollo Server?

Moving From Specification to Implementation with Apollo Server

The important thing to remember about GraphQL is that it's an open, royalty-free specification that is still under Facebook copyright. All the spec does is describe what GraphQL is supposed to do, not how it's supposed to do it. Thus, any person, group or company with the time, expertise and inclination to implement the GraphQL spec can do so. However, the better way to get up and running with a GraphQL API is to use one of the frameworks that have already been created by organizations with expertise in the technology. Apollo is one such organization.

Apollo is a commercial company that provides services and products for the GraphQL ecosystem. One of these products is Apollo Server.

Apollo Server is an implementation of the GraphQL specification for Node.js. Apollo Server abstracts away much of the complexities of programming a GraphQL API thus allowing developers to focus more on the logic and features of their GraphQL API and less on the low-level coding required to get an API up and running. While Apollo Server does not eliminate all the complexity that goes with creating a GraphQL API, it does make life easier by segmenting programming to particular areas of concern, which we'll demonstrate throughout this article.

There are some things you've got to know about the way Apollo Server "wires up" GraphQL in order to create an API. Apollo Server is a particular way of wiring up types, resolvers, subscriptions, and the basic operation types. We'll cover the details in moment. But first let's review the basics of a data graph. The concepts are essential to working with GraphQL.

Understanding Data Graphs

In order to work effectively with GraphQL you need to understand the concept of a data graph. Whereas with more traditional approaches to data management in which information is organized in tabular rows and columns typical of a relational database such as MySQL or in the document-centric way found in NoSQL technologies such as MongoDB, GraphQL considers the data graph to be the foundational way to represent data for publication. The concept behind the data graph is that data exists as distinct entities within a domain, with an entity being structured according to properties (aka, attributes or fields). In addition, an entity can have none, one or many relationships to other entities.

In discrete mathematics, the discipline that gave birth to the concept of the data graph, such entities, are referred to as nodes. The relationship between two nodes is called an edge. The demonstration application that accompanies this article captures a number of edges that can exist between nodes. (See Figure 1, below.)

Figure 1: In a data graph, an entity such as Person is called a node and the relationship between two nodes; for example, likes, is called an edge

Figure 1: In a data graph, an entity such as Person is called a node and the relationship between two nodes; for example, likes, is called an edge

Many GraphQL APIs refer to an edge as a connection. Describing an edge as a connection is a convention that evolved among the GraphQL community. For example, the demonstration application that accompanies describes a likes relationship as a likesConnection.

The edge that is default in real-world implementations of GraphQL is, has. The IMBOB API manages and presents information related to people, movies and actors. The custom object types defined in the demonstration application are movie, director, actor, and role. Figure 2 below shows the "has" relationships between various entities — a movie has directors, a director has movies, on so on.

Figure 2: The default relationship between nodes in a GraphQL API is, has

Figure 2: The default relationship between nodes in a GraphQL API is, has

The reason that it's important to understand that the "has" relationship is because it's the basis for the parent-child pattern that is typically expressed in many GraphQL APIs. Many developers who are new to GraphQL define the object graph solely in terms of the "has" edge. This is good and useful as a starting place. But, the more advanced implementations of GraphQL will move beyond has to more sophisticated edges named according to the connections convention described above.

Now that we've covered the basic concept of the data graph and how it's associated with a GraphQL API implementation, let's move onto actually creating a GraphQL API server and a GraphQL subscription server using Apollo Server 2.0.

Anatomy of the API Server

Implementing a GraphQL API using Apollo Server 2.0 involves a few steps. These steps are

  • Define the types that data graph will support
  • Define the queries and mutations by which users will perform CRUD operations
  • Define the resolvers that will provide behavior for the queries and mutations
  • Define the subscription to which users will register and listen for event messages
  • Create the schema that uses the types, resolvers and subscriptions created previously
  • Add a context definition to the schema in order to support access authentication

The details of the steps will be provided in the sections that follow.

Defining the API Types in a Typedefs File

The mentioned earlier and in the previous installment of this series, the way that GraphQL describes data structures in an API is according to a type system. You can think of a type as an object similar to those used in Object Oriented Programming. Not only does the IMBOB demonstration API use types to describe custom data structures such as movie, person and actor, types are also used to describe the basic GraphQL operations, Query, Mutation, and Subscription.

While the GraphQL specifies how a type is to be structured, they way types are implemented in an API varies by framework. One technique you can use when implementing types in Apollo Server 2.0 is to declare the types in a Javascript module file that exports the type declaration as a string in GraphQL Schema Definition Language. Then, the types definition string is assigned to the required variable typeDefs and passed onto the constructor of the schema object that will be consumed during the creation of the actual GraphQL API server.

Listing 1: A an excerpt from the file, typedefs.js that describes some of the demonstration application types declared in GraphQL Schema Definition Language (SDL)

Listing 1: A an excerpt from the file, typedefs.js that describes some of the demonstration application types declared in GraphQL Schema Definition Language (SDL)

Implementing Inheritance in GraphQL Using interface and extend

As you can see from the excerpt in Listing 1 and from the entire contents of the typedefs.js file, IMBOB uses the interface keyword to implement interface inheritance when creating a type. (Support for interfaces is a standard feature of the GraphQL specification.) Also, IMBOB uses the extend keyword defined by the GraphQL specification and supported by Apollo Server to implement object inheritance directly.

Figure 3 below shows the types and inheritance patterns defined in the IMBOB API in a dialect of the Unified Model Language. For example, notice that both the types, Person and Actor implement the Personable interface. Also, the types Movie and Cartoon implement the Movieable interface, yet Cartoon adds the field animators, which make sense. It's quite permissible to add fields not defined in an interface to a type that implements that interface.

Figure 3: A description of some of the GraphQL interfaces, types and inputTypes represented in UML (Unified Modeling Language)

Figure 3: A description of some of the GraphQL interfaces, types and inputTypes represented in UML (Unified Modeling Language)

Notice that in Figure 3 the IMBOB adds the fields, marriedToConnection, divorcedFromConnection, knowsConnection, and likeConnection to the Person type by using the extend keyword. This is an example of type inheritance implemented according to the GraphQL specification.

The purpose of the type CursorPaginationInput is to pass pagination information to the API so that it can retrieve a specific segment of items from a much longer collection. (We'll talk about supporting pagination later in this article.) PersonConnection is the return type that represents how IMBOB describes relationships (edges) between persons.

Defining custom types is an important part of creating a GraphQL API. Types represent the information available in the API.

In addition to creating custom types to represent entities that are special to IMBOB, we also need to define the Query and Mutation types that are required to do CRUD operations. CRUD (create, read, update, delete) operations.

Defining Queries, Mutations and Subscriptions

The way that GraphQL specifies implementing CRUD operations is to define two types; Query and Mutation. These are commonly known as GraphQL's basic operation types. The fields on the type Query define "read" operations. The fields defined in the Mutation type define create, update and delete operations. The IMBOB application defines Query and Mutation types along with the custom type definitions in the file typedefs.js as shown below in Listing 2.

Listing 2: The Query, Mutation and Subscription types are basic for any GraphQL Schema

Listing 2: The Query, Mutation and Subscription types are basic for any GraphQL Schema

Working with Types and Resolvers

In order to provide behavior for Query, Mutation, and Subscription types, a GraphQL API must define analogous resolvers that correspond to each field in the given type. For example, the field, movies at line 16, in Listing 2 above is the logical name that describes the query to get movies from the API.

Figure 4 below, shows the query, movies defined in the Query section of the IMBOB type definition file, typedefs.js. Notice that there is also a corresponding object, Query in a separate Resolver file, resolvers.js. The Query resolver contains a field, movies that maps to a Javascript function that contains the logic for getting movie information from the API's data storage resource. This is the behavior that gets executed when a user runs the query, movies against the IMBOB API.

Figure 4: The demonstration application's movies: [Movie] query is backed by logic provided by an analogous resolver with the same name

Figure 4: The demonstration application's movies: [Movie] query is backed by logic provided by an analogous resolver with the same name

The query-to-resolver mapping pattern is fundamental to the GraphQL specification. It's supported by all GraphQL implementations including Apollo Server. In fact, a resolver object is required when creating the schema that gets passed as a parameter when constructing an Apollo Server. (this exact concept will be covered in the section, Creating the API and Subscription Servers that appears later in this article.)

Once Query and Mutation types and their associated resolvers are declared, we need to define the Subscription type that allows IMBOB to emit messages asynchronously to registered listeners.
 

Adding Events and Messaging Using Subscriptions

The typical API pattern whereby an API-consuming client "calls" an API is incredibly inefficient when the data it is interested in is constantly changing (like a stock-ticker). In order for the client to keep up with the changes, it must constantly poll the API and then execute some business logic on the result to see if something has changed. It's a waste of both compute and networking resources.

The more efficient way to handle such "evented" workflows (workflows that are triggered by the occurrence of an event, like a change in stock price) is for the client to tell the API server that it wants to be notified when such an event has happened. In broader terms, this idea of pushing or streaming updates to a client is sometimes called publish and subscribe or pubsub. The API server publishes messages to certain topics to which clients can subscribe.

Unfortunately, with the REST architectural style for APIs, there is no built-in mechanism for publishing or subscribing to topics. As a result, there are several bolt-on approaches including webhooks and websockets that REST API providers turn to in order to offer such "evented APIs."

With GraphQL however, the idea of evented APIs is built-in (though the semantics of this built-in approach are a bit unintuitive at first). In order for a client to register interest in an event-driven topic, the server must first make that topic available as a GraphQL "subscription." A client can then register its interest with that GraphQL subscription, or, at the API provider's option, the client can indicate interest in a more granular level of that subscription known as a "channel."

In either case, once the client has registered its interest with the API server, the API server creates a queue that's specific to that client. Once an event fires (ie: a change in stock price), the queue is stuffed with the details of the event. Those details could be a simple reflection of the event that occurred (ie: stock ticker and its new price) or a processed version of the event (ie: the stock ticker, the new price, and its net impact on the value of the end-user's stock portfolio, etc). Then, it's the client's responsibility to see if a new event is waiting for it in its queue.

The IMBOB demonstration application provides a number of subscriptions available for clients.

One such subscription is onMovieAdded(). A user looking to get real-time updates from the onMovieAdded() subscription would use the following query in GraphQL query language to register with the subscription:

subscription onMovieAdded{
  onMovieAdded(channelName:"MOVIE_CHANNEL"){
    id
    name
    createdAt
    storedAt
    body
  }
}

The user is then "wired" into the API and will receive messages from the API as each movie is added. The message will adhere to the format described by a custom type. In the case of IMBOB the custom type is, Event which is declared like so:

"""
Event is a type that describes messages emitted
from a subscription.
"""
type Event {
   id: ID
   name: String
   createdAt: Date
   storedAt: Date
   body: String
}

That the name of the type representing a message is Event is incidental. Type naming for messages is arbitrary. Neither "Event" nor "event" are reserved words in GraphQL. We could have just as easily called the response message, OnMovieAddMessage. The reason that the generic name Event is used in IMBOB is to have a single type to describe messages emitted by the IMBOB API. This is special to IMBOB. Other APIs might have a different structure for the messages they return.

Figure 5 below shows the way to subscribe to, onMovieAdded within the GraphQL Playground.

Figure 5: Subscribing to onMovieAdded events

Figure 5: Subscribing to onMovieAdded events

To demonstrate how the act of adding a new movie can trigger the sort of event that a subscription is listening for, listing 3 below is an example of a mutation to add a movie to the IMBOB API using the GraphQL query language. In the real world, a mutation is likely to be coded with variable names as opposed to the actual data as shown below (for tutorial readability and demonstration purposes).

mutation{
	addMovie(movie:{title:"The Great Escape", 
                  releaseDate:"1963-07-04"}) {
    id
    title
  }
}

Listing 3: The mutation for adding a movie to IMBOB

Figure 6 below shows the actual mutation for addMovie executed in GraphQL Playground.

 Figure 6: The mutation, addMovie is declared to return the fields, id and title upon success.

Figure 6: The mutation, addMovie is declared to return the fields, id and title upon success.

The right panel highlighted with a red border, in the GraphQL Playground screenshot in Figure 7 below shows that a message was emitted out of the API upon a movie being added to the system, as shown above in Figure 6.

Figure 7: Once a user subscribes to onMovieAdded, the UI will display messages emitted by the API for the subscription when a movie is added to the system.

Figure 7: Once a user subscribes to onMovieAdded, the UI will display messages emitted by the API for the subscription when a movie is added to the system.

Notice that in Figure 7 the Event message fired by onMovieAdded (as shown in the right panel highlighted by a red border) contains the fields id, name, createdAt,storedAt and body. These are the "fields to return" that were defined when we registered to the subscription. (See the left panel in Figure 7 above.)

The important thing to understand is that GraphQL in general and IMBOB in particular supports subscriptions. A user registers to a subscription then continuously listens for messages coming out of the API in response to a query or a mutation. Connection to the subscription is constant until the user disconnects. (You can disconnect from a subscription in the GraphQL Playground by closing the tab in which the subscription registration was made.)

Now that we've covered subscriptions at the user level. Let's look at how to program subscriptions under Apollo Server.

Defining a Subscription

As mentioned at the beginning of this article, a Subscription is one of the basic operations types specified by GraphQL. They are defined in somewhat the same manner that queries or mutations are implemented. You define the type Subscription in the typedefs.js file. Then, apply fields to the type that was just defined, with each field representing a particular subscription. However, unlike queries and mutations in which you program resolvers to provide CRUD behavior, subscriptions are a bit different. With a subscription you are going to use the PubSub package that ships with Apollo Server to emit a message from resolver behavior that's associated with a particular query or mutation. For example, we'll publish a message to subscribers of onMovieAdded from within the resolver, addMovie().

Granted, this dynamic of implementing subscription behavior by publishing from within a resolver method can be a bit confusing. It can take time to develop a clear understanding of the technique, particularly if you are new to message driven architectures. Working with a concrete example makes things easier to understand.

Let's start by taking a look at how IMBOB implements subscriptions.

The first place to start is by looking at how IMBOB creates a Subscription type. Figure 8 below shows the definition of the type Subscription. Each field in the type names a particular subscription, The subscriptions of interest for this discussion are:

  • onMovieAdded(channelName: String): Event
  • onMovieUpdated(channelName: String): Event

These fields are highlighted in bold in the figure below.

Figure 8: The onMoviedAdded and onMovieUpdated Subscriptions returns the custom defined Event object

Figure 8: The onMoviedAdded and onMovieUpdated Subscriptions returns the custom defined Event object

Let's examine the details of the field, onMovieAdded.

onMovideAdded(channelName: MovieChannelName): Event

The declaration of onMovieAdded has 3 parts.

onMovideAdded is the name of the subscription

channelName defines an associated channel that can have a value from the custom defined GraphQL enum, MovieChannelName, (see Listing 2.)

onMovideAdded is the name of the subscription

Event is the particular return type (based on how it was defined earlier). It is the message emitted by the subscription

The following describes each part.

Declaring a Channel

channelName: MovieChannelName is a parameter that indicates a channel that is supported by the subscription. You can think of a channel as a named stream from which messages flow. Channel names are defined in a GraphQL enum, MovieChannelName, that is predefined by the IMBOB API and is special to the API. A user can declare a channel when registering to a subscription. Then, not only will the user receive messages particular to the subscription, but also particular to the channel. In other words, channels add a finer grain to message emission for a particular subscription.

Figure 9 below shows an excerpt from the IMBOB API documentation published by the Apollo Server GraphQL Playground. The excerpt describes the subscription, onMovieAdded. (Remember, one of the nice things about GraphQL is that it is self-documenting. By default all type, query, mutation, and subscription information is published automatically via GraphQL introspection. And, when you comment the type definitions and fields using three double quotes (""") to open and close a comment, that comment will appear in the documentation too.)

Figure 8: The onMoviedAdded and onMovieUpdated Subscriptions returns the custom defined Event object

Figure 9: Apollo Server's GraphQL Playground takes advantage of the introspection feature of GraphQL to automate online documentation for an API.

Notice that the documentation shown above in Figure 9, indicates that onMovieAdded has the parameter channelName that can be used to bind the subscription registration to a particular channel. As you can see in the documentation, IMBOB supports four channels: MOVIE_CHANNEL, HORROR_MOVIE_CHANNEL, DRAMA_MOVIE_CHANNEL, COMEDY_MOVIE_CHANNEL. These channels are special to the IMBOB API.

Different API's will have their own channels and related conditions by which messages will be emitted. In the case of IMBOB, messages are emitted according to the genre of the movie in question. If no channel is declared, the subscriber will receive all messages published to a particular subscription, regardless of the channel.

Declaring a Message Type

The aforementioned custom type Event describes the structure of the message that will be emitted by a subscription running under IMBOB. Figure 10 below shows an excerpt of the GraphQL documentation for the type Event.

Figure 10: The custom Event object describes the data sent to listeners of the onMovieAdded and onMovieUpdated Subscriptions

Figure 10: The custom Event object describes the data sent to listeners of the onMovieAdded and onMovieUpdated Subscriptions

Figure 10 above not only shows the documentation that describes the fields that are part of the Event type, but also show the details of the particular field body.

The important thing to understand about subscriptions and the message type (also known as return type) is that when you define a subscription in the typeDef file, you need to also declare the type of the message the the subscription will emit. As mentioned above, different APIs will emit different types that satisfy the purpose of the API. In the case of IMBOB, there is but one message type at this time: Event.

Now that we've covered how to define a subscription in the file typedefs.js, let's examine the way IMBOB emits a message in response to behavior in a particular resolver.

Publishing a Subscription Message

Listing 4, below shows the code for the resolver, addMovie which is associated with the mutation, addMovie that is defined in the type definition file of IMBOB, typedefs.js. The file that contains this resolver code is resolver.js.

Listing 4: The resolver, addMovie adds movie information to the IMBOB data store and also publishes information.

Listing 4: The resolver, addMovie adds movie information to the IMBOB data store and also publishes information.

Notice that addMovie is bound to an asynchronous Javascript function (running under Node.js). The Javascript function declares two parameters, parent and args. The function expects the values for these parameters to be passed into the resolver by Apollo Server when the mutation, addMovie is called. Passing these parameters into the resolver is part of the "automatic wiring" that Apollo Server provides.

Continue on page 2.

Be sure to read the next GraphQL article: How GraphQL Delivers on the Original Promise of the Semantic Web. Or Not.

 

Comments (0)