GraphQL concepts I wish someone explained to me a year ago

Part 3: Queries (client implementation)

Naresh Bhatia
Naresh Bhatia
9 min readDec 22, 2018

--

Image by Rostyslav

In part 2 of this series we implemented a GraphQL server that could respond to queries. In this part, we will build a matching client using React.

One part will be released every day this week. Follow me or subscribe to my newsletter to get updates on this series.

Source Code

You can download the code for this part by executing the following commands. Skip the first command if you have already cloned the repository.

git clone https://github.com/nareshbhatia/graphql-bookstore.git
cd graphql-bookstore
git checkout 3-queries-client

Test Drive

Before diving into the implementation, let’s look at how the final implementation works. Make sure you have yarn installed on your machine. Now execute the following commands:

cd apollo-bookstore-server
yarn
yarn dev

This starts the server. In a different shell, execute the following commands to start the client:

cd apollo-bookstore-client
yarn
yarn start

Point your browser to http://localhost:3000/. You should see the GraphQL Bookstore client. It shows all the bookstore entities and relationships on a single page. This will allow us to demonstrate several GraphQL features — for example, how Apollo Cache normalizes entities to keep views consistent across the application.

GraphQL Bookstore client

Client Implementation

The client has been created using create-react-app. I am using TypeScript to benefit from type checking. The UI has been created using Material UI. I am also using mobx-state-router for routing and formik for form handling. Most importantly, I am using the Apollo Client to send GraphQL queries to the server.

Side note
Apollo offers a slightly simpler starter-kit called Apollo Boost. However, as of this writing, it does not support subscriptions. Hence I chose to use the Apollo Client directly.

Setting up Apollo Client

In order to use Apollo Client, we need to make it available to any component that needs to send GraphQL queries to the server. This is done at the App level, in the app.tsx file. Here’s the gist of it (I have omitted code that is not relevant to this discussion):

// ----- app.tsx -----

// Create an http link:
const httpLink = new HttpLink({
uri: process.env.REACT_APP_HTTP_LINK
});

// Create a WebSocket link:
const wsLink = new WebSocketLink({
uri: process.env.REACT_APP_WS_LINK
});

const link = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' &&
operation === 'subscription';
},
wsLink,
httpLink
);

// Create the Apollo client
const client = new ApolloClient({
link: link,
cache: new InMemoryCache()
});

export class App extends React.Component {
public render() {
return (
<ApolloProvider client={client}>
<Shell />
</ApolloProvider>
);
}
}

The first half of the code is essentially creating a link object. The Apollo Client needs a regular HTTP link for queries and mutations and a WebSocket link for subscriptions. Hence we create an httpLink and a wsLink and combine them into a single link object. This link object is then passed into the constructor of ApolloClient. In addition, we send an instance of InMemoryCache, which is a normalized data store. You can read more about it here.

After creating an instance of the ApolloClient, we need to make it available to all the components in the component hierarchy. This is where the ApolloProvider comes in. It places the client in the React context, making it available to all the nested components.

Component Hierarchy

Now let’s look at the component hierarchy of the application. The main page of the application is called the HomePage.

Component hierarchy

HomePage is divided into three panels showing publishers, authors and books. The panels are wrapped by container components, responsible for fetching data from the server and handing it to the panels. The panel components are purely presentational, they know nothing about GraphQL or how the data was fetched (see Presentational and Container Components by Dan Abramov). Let’s see how data is fetched from the server by looking at a sample container component.

PublishersContainer

PublishersContainer is responsible for fetching the list of all publishers and handing it to the PublishersPanel for display.

# ----- components/publishers/publishers-container.tsx -----import { DefaultQuery } from '..';
import { PublishersPanel } from './publishers-panel';
import { GET_PUBLISHERS } from './publishers-queries';

export class PublishersContainer extends React.Component {
render() {
return (
<DefaultQuery query={GET_PUBLISHERS}>
{({ data: { publishers } }) => {
return <PublishersPanel
publishers={publishers} />;
}}
</DefaultQuery>
);
}
}

The heart of PublishersContainer is the nested <DefaultQuery> component. It takes a GraphQL query as a prop and calls the render function that is provided as a child. The render function is called with an object containing a data property that holds the query result. You might recognize this pattern as the popular Render Props pattern. The render function in turn calls PublishersPanel with the query result.

The DefaultQuery component itself is a thin wrapper around Apollo’s Query component. It abstracts away a couple of common tasks we want to do for every query: (1) show a loading message and (2) show any errors returned by the query. Here’s the code for DefaultQuery:

# ----- components/basics/default-query.tsx -----import { Query } from 'react-apollo';
import { CenteredMessage } from './centered-message';

export const DefaultQuery = ({ query, variables = {}, children }) => (
<Query query={query} variables={variables}>
{({ loading, error, data, fetchMore, subscribeToMore }) => {
if (loading) {
return (
<CenteredMessage>
Loading...
</CenteredMessage>
);
}

if (error) {
return (
<CenteredMessage>
{error.message}
</CenteredMessage>
);
}

return children({ data, fetchMore, subscribeToMore });
}}
</Query>
);

Finally, lets look at the GET_PUBLISHERS query that is tucked away in a separate module (so that it can be reused):

# ----- components/publishers/publishers-queries.tsx -----import gql from 'graphql-tag';

export const GET_PUBLISHERS = gql`
query GetPublishers {
publishers {
id
name
}
}
`;

First off, you will notice that the query string is wrapped in the gql function. This function parses the query string and returns a GraphQL AST (abstract syntax tree). While the query string is easier for humans to read, Apollo Client requires it in the form of an AST. That’s the reason for wrapping the query string with the gql function.

The second thing you may notice is that we have given a name to the query: GetPublishers. This is not a required part of the GraphQL syntax — we have been using queries in GraphQL Playground without a name. However in the client we will always name our queries so that we can leverage type safety using TypeScript. Here’s how it works:

  • We first use the Apollo CLI to download the bookstore schema from our GraphQL server. The script to do this is in the client’s package.json file: gql:schema. The schema is saved as schema.json in the client’s root directory and is also committed to the repo. Whenever the schema on the server changes, we must run gql:schema again to update the client’s copy.
  • Once we have a local copy of the schema, we must run Apollo CLI’s code generator to generate TypeScript type definitions for all our queries. As mentioned in part 1, query responses contain whatever the query asks for — not the full domain objects. Hence each query is unique and needs unique TypeScript types to describe its inputs and outputs. While this can be done by hand, it would be very tedious and error prone. Hence we use the code generator to generate the types automatically from the queries. The script to generate these type is in the client’s package.json file: gql:types. When this script is run, it parses all the queries in the client and generates TypeScript type definitions in folders named __generated__ relative to where it found the query. For example, here’s the type definition generated for the GetPublishers query:
/* tslint:disable */
// This file was automatically generated and should not be edited.

// ====================================================
// GraphQL query operation: GetPublishers
// ====================================================

export interface GetPublishers_publishers {
__typename: 'Publisher';
id: string;
name: string;
}

export interface GetPublishers {
publishers: GetPublishers_publishers[];
}

This needs a bit of explanation. The interface GetPublishers represents the type of the query response. In this case it is an object with one property called publishers which is an array of GetPublishers_publishers. Now that’s a mouthful! Why is the type of publishers GetPublishers_publishers and not just Publisher? Again, in GraphQL we don’t receive the full domain object, we receive only the slice we asked for. Hence this type name was custom created for this specific query. Also note the __typename property of GetPublishers_publishers — it’s an internal property that Apollo Client uses to normalize the cache.

Now that we know how these types are generated, let’s look at where they’re used next — the PublishersPanel component.

PublishersPanel

PublishersPanel is responsible for receiving a list of publishers and displaying them. It is a fairy generic React component that uses a Material UI table to display publishers. The only special thing to note is that we are using the generated publisher type to make sure that we are using the publisher list in a type safe way. As a side benefit, it also enables autocompletion in editors that understand TypeScript. Here’s the relevant code in PublishersPanel:

import { GetPublishers_publishers }
from './__generated__/GetPublishers';

export interface PublishersPanelProps {
publishers: Array<GetPublishers_publishers>;
}

export class PublishersPanel extends React.Component<PublishersPanelProps> {
public render() {
const { publishers } = this.props;

return (
<React.Fragment>
...
{publishers.map(publisher => (
<TableRow key={publisher.id}>
<TableCell>
{publisher.name}
</TableCell>
</TableRow>
))}
...
</React.Fragment>
);
}
}

BooksContainer

Now that you understand PublishersContainer and PublishersPanel, you understand the remaining container and panel components as well — they are essentially the same. The only odd one out is the BooksContainer. The reason is that it needs more information from the server to pass down to the BooksPanel. In addition to the list of books, the BooksPanel requires the list of all publishers and authors. This is so that it can show drop downs of publishers and authors when we are creating and editing books (yes, mutations are coming up next). When I first came across this use case, I was scratching my head because the <Query> component accepts only one query, and I needed three! Fortunately, I found a neat trick in the Apollo docs — the <Query> component can be nested to run multiple queries within a single component. So that’s what I did in BooksContainer:

<DefaultQuery query={GET_BOOKS}>
{({ data: { books } }) => {
return (
<DefaultQuery query={GET_AUTHORS}>
{({ data: { authors } }) => (
<DefaultQuery query={GET_PUBLISHERS}>
{({ data: { publishers } }) => (
<BooksPanel
books={books}
authors={authors}
publishers={publishers}
/>
)}
</DefaultQuery>
)}
</DefaultQuery>
);
}}
</DefaultQuery>

One more thing we need to look at is the GET_BOOKS query — it’s a little different:

# ----- components/books/books-queries.ts -----import gql from 'graphql-tag';

export const BOOK_FRAGMENT = gql`
fragment BookFragment on Book {
id
name
publisher {
id
name
}
authors {
id
name
}
}
`;

export const GET_BOOKS = gql`
query GetBooks {
books {
...BookFragment
}
}

${BOOK_FRAGMENT}
`;

Note that the GET_BOOKS query does not list out every field that it wants in the result. Instead it includes a BookFragment. A fragment is simply a shared piece of query logic. We want to return the fields specified in BOOK_FRAGMENT from multiple queries (that we will write later). So we have factored them out into a fragment and we use that fragment in the main GET_BOOKS query.

Summary

We’ve now covered the full client implementation for queries. Let’s look at what we’ve learned:

  • How to set up Apollo Client so that it is available to any component that needs to send out queries
  • How to setup our app using the Container and Presentational Components pattern to segregate responsibilities of fetching from displaying data
  • How to use Apollo’s <Query> component to execute GraphQL queries
  • How to nest the <Query> component to execute multiple queries from a single component
  • How to use fragments to factor out common parts of a query
  • How to use Apollo CLI to generate TypeScript type definitions

We’ve built quite a bit so far — are you enjoying the series? I‘d love to get your questions and comments.

In part 4, we’ll dive into mutations and add them to the Bookstore server.

Read Part 4: Mutations (server implementation) >

Resources

  • GitHub Explorer: A slightly more sophisticated application that demonstrates GitHub’s GraphQL API. It uses some elaborate queries and mutations.
  • Apollo Client Developer Tools: A Chrome DevTools extension for Apollo Client. It’s a must have. It provides a built-in GraphiQL console, a query watcher, a mutation inspector and a cache inspector.

--

--