GraphQL is a query language, which allows data to be queried in a JSON-like form from a GraphQL interface. Unlike REST, GraphQL does not focus on individual resources with their respective URLs, but on a single GraphQL schema, which is offered by the server. Clients can send queries to this schema and receive the corresponding data from the server. The schema is statically typed, which means that the schema’s developers specify types, which describe what kind of data is offered, and how these correlate to each other. Listing 1 shows an example of a request, as it might occur in a blogging application, with blog articles, authors, and comments. All articles in the listing are queried, but not all of their field are included – only their id
and title
. The query also contains the authors and comments for each article. We are thereby able to query for the exact data, which is currently required, in a single request. A JSON structure, which corresponds exactly with this query, is the response.
Listing 1
{ articles { id title authors { name } comments { text } } }
There are several options to actually use GraphQL in a client application. Theoretically, it would be possible of course to manually send the queries and in turn to manually prepare the result data for the display. However, there are also frameworks that can support developers in using GraphQL. This is especially useful in times of declarative UI libraries such as React, because it is also possible to declaratively describe data requirements besides the UI. A component then no longer states imperatively how the data is fetched, but only which data is required.
The Apollo Client, which is available as open source software, is such a framework that supports this kind of development. Apollo enjoys a good reputation within the GraphQL community and is probably the most widely used client framework for GraphQL applications. But above all, it is also available for various UI frameworks, including React, Angular, Vue.js, native Android, and iOS applications. In this article, we want to focus on the integration and interaction with React, but we will also take a brief look at Angular at the end.
The already mentioned blogging platform will serve as an exemplary context. The complete source code of this application can be found on GitHub, server included, which serves as the application’s backend, and provides a ready-made schema. We only need a current Node.js installation to start the React application, then we can use npx create-react-app <projekt-name>
to create the app’s basic framework. The next step is to add some JavaScript packages for the Apollo framework: npm install --save apollo-client apollo-cache-inmemory apollo-link-http graphql graphql-tag react-apollo
. We will take a closer look at what these packages do exactly in a moment’s notice.
The basic idea behind Apollo is that there is a store in the fronted, which manages the application data. UI components describe which data they need via GraphQL, and receive this exact data from the Apollo store. The store takes care of loading the data from the server, if required, and manages them internally. A local cache is used for this purpose, in order to store the data. Therefore, it is transparent to the UI component whether a request from a UI component is supplied from the cache or from the server.
Set-up
The first step is to set up this exact Apollo store and integrate it into the React application. The code can be seen in Listing 2. The existing index.js file
will be adjusted accordingly.
The store is created with the class ApolloClient
. For the configuration, the values link
and cache
must be filled. The link is used to tell Apollo where to find the server’s GraphQL schema. In the example, the URI is given as a relative path. Alternatively, complete URIs in the form of http://example.com/api/graphql
are also possible.
The second parameter defines how the local caching shall take place. InMemoryCache
is the usual variant, which stores data in the memory, but only for the duration of the browser session. There are, however, also alternative implementations, which use the LocalStorage
functionalities of browsers, for example, to keep the data permanently available in the browser. In this way, they can also be refilled after a reload of the browser without new network communication.
After that, the ApolloClient
has to be made available for React. This is done via the component ApolloProvider
. This wraps our actual app component and ensures that all underlying React components can use GraphQL.
Listing 2
import { ApolloClient } from "apollo-client" import { HttpLink } from "apollo-link-http" import { InMemoryCache } from "apollo-cache-inmemory" import { ApolloProvider } from "react-apollo" const apolloClient = new ApolloClient({ link: new HttpLink({uri: "/api/graphql"}), cache: new InMemoryCache() }); ReactDOM.render( <ApolloProvider client={apolloClient}> <App /> </ApolloProvider> , document.getElementById('root'));
Queries
This completes the basic setup and we can write a React component, which retrieves and displays data. Therefore, we want to build a component that provides an overview of the existing blog articles. The first step is to consider which data the component needs, and what the corresponding GraphQL query looks like.
It is quite common to pack the query into the same file that contains the actual component. This achieves a high level of encapsulation, since a developer who uses the component can no longer see which data sources are addressed and how. Therefore, you only need to touch a single point, if you need to make changes to the query and the display.
The query is stored in a local variable and created with the function gql
from the graphql
-tag package, as shown in Listing 3. A relatively new JavaScript technique, called Tagged Template Strings, is used. This allows multi-line strings to be provided with template parameters, which in turn can be modified directly by a function (in our case gql
). It is important that the string is enclosed with back ticks (also known as the French Accent Grave) and not with normal single or double quotation marks.
The gql-tag
function parses the query string and creates an abstract syntax tree, AST for short, which is then used to execute the query. This is done with the React component Query
, which is imported from the react-apollo
package. It accepts the previously defined query variable as an argument and takes care of executing the query. In order to react to the result, a function is defined as a child element, which is automatically called by Apollo as soon as a new state has set in with regard to the query.
Listing 3
import gql from "graphql-tag" const articleQuery = gql` query articlesQuery { articles { id title text authors { id name } } } `
To do so, the function receives the arguments loading
, error
and data
. loading
is a Boolean flag, which expresses whether we are currently still in the process of loading or whether it has already been completed. If an error occurred during loading, the error
argument is filled. If successful, the data
argument contains the result data.
Within the function, you can react to this information and define how the display should look. In the example in Listing 4, a corresponding text is generated in both loading
and error cases
.
In the case of success, the data of the result set is iterated and an ad is generated for each blog article.
Listing 4
const ArticleOverview = () => ( <Query query={articleQuery}> {({loading, error, data}) => { if (loading) { return <div><p>Loading...</p></div> } if (error) { return <div><p>Error: {error}</p></div> } return <div> {data.articles.map(article => ( <div key={article.id}> <h2>{article.title}</h2> <div> {article.text} </div> </div> ))} </div> }} </Query> )
Here, the declarative character of React also becomes apparent: There is no imperative reaction to events, and the DOM tree of the component is selectively updated correspondingly, as would be the case with classic jQuery applications, for example. Instead, a description is written that maps the state of the component on to a matching DOM description at any given point in time.
Mutations
Now we can display server data in the client via GraphQL. Most web applications must also be able to change data and trigger actions in the server though. In GraphQL these two aspects are separated from each other. In the schema of the application, the types Query
for all offered queries and Mutation
for changes exist at the highest level (additionally, there is still a Subscription
, which we do not want to take a closer look at at this point). In contrast to REST, GraphQL allows you to define change operations in independence from the data model of the query page. Mutations in GraphQL are more similar to remote procedure calls (i.e. function calls), instead of uniformly defined operations on the same resources as it is the case with REST. Depending on the use case, the offered functions can also be strongly oriented towards business, but of course, simple CRUD operations (Create, Read, Update, Delete) can also be defined. In the example, we assume that our server offers a mutation like in listing 5, which allows adding a comment as a guest, who is not logged in.
Listing 5
type Mutation { addCommentAsGuest( articleId: ID! authorName: String! text: String! ): Article }
This Mutation
function accepts as a mandatory parameter (marked by exclamation marks) the ID of the article to which the comment belongs, the name of the author, and the comment-text. In GraphQL, mutations always have a return type, in our case Article
. Usually, these should be the objects changed by the mutation, so that a client can immediately display the changed data without having to start another query.
For adding comments, we again create our own component AddComment
. This component also consists of two parts: the GraphQL-Query and the code for display. The query can be seen in listing 6. Once again, the query is stored in a variable via the gql-tag
function: a kind of local function definition with the type mutation
is created in the first line of the query. The name we used here, as well as the identifiers for the parameters, can be freely chosen theoretically, but we use the name given to us by the schema as guidance. In the second line, the actual mutation of the schema is called, and the parameters of the external function are passed on. What may look like an unnecessary duplication at first, often turns out to be useful, because the flexibility is increased. The Mutation
function is followed by an ordinary GraphQL query, which returns some of the changed data of the article. Here the IDs are especially important, because Apollo uses them to synchronize its local cache with the new data from the server.
This local cache also allows all other locations, in which components display affected data, to be updated automatically, after the mutation has been executed – without requiring an additional request to the server.
Listing 6
const addCommentMutation = gql` mutation addComment($text: String!, $authorName: String!, $articleId: ID!) { addCommentAsGuest(text: $text, authorName: $authorName, articleId: $articleId) { id comments { id text guestAuthor } } } `
In addition to the GraphQL mutation, we now also create the React component, which contains a form for entering the comment and actually executes the mutation after clicking a button. The source code can be seen in Listing 7.
At this point, it makes sense to separate the two aspects mentioned above, i.e. the display of a form, and the placement of the mutation query. For this purpose, we first define a React component AddCommentForm
, which itself has no dependencies to GraphQL or Apollo, but only displays the form and manages the local state of the form.
As is usual for React forms, the local state of the form fields is updated using the setState
method. When sending the form, we first prevent the standard behavior for HTML forms, namely the sending of a request, and the subsequent reloading of the page. Instead, we want to use JavaScript code to react directly. We assume here that an addComment
function was passed to the component from outside, to which we only have to pass the necessary values for the execution of the mutation. We take the text and the author’s name of the commentator from the local state of the form. We also expect the comment’s article ID to be an external value.
We keep the AddCommentForm
component as an implementation detail within our JavaScript file. In order to actually execute the mutation, we create another React component, AddComment
, which we also make visible to the outside by export default
. Similar to the ArticleOverview
component above, we also use a mutation
component provided by the Apollo framework, which takes care of the actual work. We only have to pass our mutation query variable and define how we want the display to look. For this purpose, a function is defined again as a child element, which contains the GraphQL mutation as a JavaScript-function-argument. The name we used here corresponds to the one that we used as the local function name in our mutation query. We pass this function directly to our AddCommentForm
component. As with the ArticleOverview
component, it would also be possible to add further function parameters here. You could, for instance, react to the current loading state of the query or you could manually restart the mutation. But in this simple example, we want to do without it.
Listing 7
class AddCommentForm extends React.Component { constructor() { super() this.state = {name: "", text: ""} } render() { return <div> <form onSubmit={e => { e.preventDefault() this.props.addComment({ variables: { text: this.state.text, authorName: this.state.name, articleId: this.props.articleId } }) }}> <div> <label>Author:</label> <input type="text" value={this.state.name} onChange={e => this.setState( {name: e.target.value} )}/> </div> <div> <textarea value={this.state.text} onChange={e => this.setState( {text: e.target.value} )}/> </div> <button type="submit">Add Comment</button> </form> </div> } } const AddComment = ({articleId}) => ( <Mutation mutation={addCommentMutation}> {(addComment) => <AddCommentForm articleId={articleId} addComment={addComment}/> } </Mutation> ) export default AddComment
Now we’ve seen both of the Apollo Client framework’s essential parts in action. The novelty of the Apollo framework is that on the one hand there is a UI framework agnostic part (which includes, for example, the store and the caching), and on the other hand there are UI framework specific libraries (for the pleasant use of the general functionality in a familiar way). The variant, which is shown here (in which UI components can accept functions as child elements and, depending on the context, can call them up for display), corresponds to a pattern that is popular with the React community and it is called Render Props. In a framework designed with an object orientation mind, such as Angular, a functional variant such as this would probably be rather unusual or maybe even impossible from a technical perspective.
Angular
However, the Apollo Client can also be used with Angular, due to the UI-Framework specific integration libraries. Therefore, in the last part of this article, we will briefly build an Angular component for a comparison that provides the same article overview. We can reuse the query string from Listing 3 as it is. As it is usual for Angular, we create fields in the component class for the data to be displayed. In our case this is loading
of the Boolean
type, as well as article
as arrays. In this simple example we do without explicit typing and, therefore, we use any
for the article array as the type. In real projects one would certainly create dedicated TypeScript types. To get access to Apollo Client, we injected an instance of Apollo
into the constructor. In order to work, Angular’s module system has to be configured accordingly beforehand of course.
To connect the component to the Apollo Client, we use the OnInit
Lifecycle Hook of Angular. In the corresponding ngOnInit
method we call the method watchQuery
from Apollo and pass the GraphQL query from Listing 3. As usual with Angular, RxJavaScript streams are used here as well. Accordingly, the watchQuery
method returns such a stream, on which we can ultimately subscribe in order to be notified when data for this query has changed. In the subscriber, we react to the new data and store it in the corresponding fields of our class. To resolve the subscription after removing the component from Angular, we should also implement an OnDestroy
Lifecycle Hook. To do this, we also create a field in the class for the subscription itself. The set-up of the GraphQL query is now complete. Now all that is missing is a template, which displays the corresponding data. The whole component can be seen in Listing 8.
Listing 8
@Component({ selector: 'app-articles-overview-page', template: ` <div *ngIf="loading"> <p>loading</p> </div> <div *ngIf="!loading"> <div *ngFor="let article of articles"> <h2>{{article.title}}</h2> <div>{{article.text}}</div> </div> </div> `, }) export class ArticlesOverviewPageComponent implements OnInit, OnDestroy { loading: boolean; articles: Array<any>; private querySubstription; constructor(private apollo: Apollo) { } ngOnInit() { this.querySubstription = this.apollo.watchQuery<any>({ query: articleQuery }) .valueChanges .subscribe(({data, loading}) => { this.loading = loading; this.articles = data.articles }) } ngOnDestroy() { this.querySubstription.unsubscribe() } }
Conclusion
We have seen that developing frontend applications with GraphQL and Apollo Client is not difficult. The aspect that can be abstracted from the technical details of the loading process in the UI components is especially interesting. Instead, the Apollo framework decouples and optimizes via the local cache. At the same time, Apollo also offers extensive options for fine-tuning the caching and loading mechanisms if required. The Apollo Client Developer Extension, offered for the Chrome DevTools, is also helpful here. This allows a deeper insight into the state of the local cache in order to get to the bottom of possible problems. Another interesting aspect of Apollo Client is that it is available for different UI frameworks. This is especially interesting in contexts, in which different technologies are to be used together.
But the Apollo Client is not the only framework that allows working with GraphQL. The biggest competitor is probably Relay, developed by Facebook. As a framework, it is limited to React or React Native.