Creating, configuring, and using an Apollo GraphQL client

Now that we have defined our queries and that we have generated code for those using Apollo, we can start using them.

We will now create and use an Apollo Client with the help of Apollo-Boost. Apollo-Boost isn't strictly necessary, but it will heavily simplify our implementation.

We will use the Apollo Client within our Home component.

When the search function gets invoked by the Search component, we will use the Apollo Client to search for artists and songs. Also, when the user selects a song, we will use it again to retrieve its lyrics.

First of all, let's create our Apollo Client instance.

Let's get started:

  1. Open the frontend/src/pages/home.tsx file.
  2. Then, add the following declaration right after the import statements:
import ApolloClient from 'apollo-boost';

const apolloClient = new ApolloClient({ uri: 'http://localhost:4000/graphql' });

As you can see, we simply had to instantiate the ApolloClient class provided by Apollo-Boost and point it to the GraphQL endpoint that we want to send our queries to.

Before we start sending our queries, we need to add some states using the useState hook. We will use that state to store the search results.

Add the following declarations at the beginning of the Home page (within the function's body):

const [foundArtists, updateFoundArtists] = useState<FindArtists_artists[]>([]); 
const [foundSongs, updateFoundSongs] = useState<FindSongs_songs[]>([]); 

Notice that we have specified the type of our state using the generic argument of useState. In the case of the foundArtists state, we know that we will get back an array of FindArtists_artists objects. For the foundSongs state, we will store an array of FindSongs_songs objects.

Then, add the following imports to make it work:

import { useState } from 'react';
import { FindArtists_artists } from '../generated/FindArtists';
import { FindSongs_songs } from '../generated/FindSongs';

Now that we have our state, we can finally execute our queries and store the results that we get back.

We have covered this syntax in the previous chapter, but here's a quick reminder just in case. When we call useState, we pass it the initial state—in this case, an empty array. In return, we receive a tuple back, out of which we extract (using array destructuring) two things that we need to treat as constants. The first one is the current value of this piece of state and the second is the function that we can use to safely update it.

Modify the search function in the Home view, as follows:

const search = (searchText: string) => { 
    console.log(`Home: searching for ${searchText}`); 
 
    apolloClient.query<FindArtists, FindArtistsVariables>({ 
        query: FindArtistsQuery, 
        variables: {value: searchText} 
    }).then((result: ApolloQueryResult<FindArtists>) => { 
        console.log('Home: found artists: ', result.data); 
        updateFoundArtists(result.data.artists); 
    }); 
 
    apolloClient.query<FindSongs, FindSongsVariables>({ 
        query: FindSongsQuery, 
        variables: {value: searchText} 
    }).then((result: ApolloQueryResult<FindSongs>) => { 
        console.log('Home: found songs: ', result.data); 
        updateFoundSongs(result.data.songs); 
    }); 
}; 

And add the following imports at the top of the file:

import { FindArtists, FindArtistsVariables } from '../generated/FindArtists';
import { FindSongs, FindSongsVariables } from '../generated/FindSongs';
import { FindArtistsQuery, FindLyricsQuery, FindSongsQuery } from '../graphql/queries';
import { ApolloQueryResult } from 'apollo-boost';

Here, we execute two different GraphQL queries one after another using our Apollo Client: the first to find artists and the second to find songs. This mimics what we did in the first version of the application. Note that the responses to each query will be received asynchronously, hence the .then(...​) function that we define.

The responses could also be received out of order, but it doesn't matter in our case.

As you can see from the preceding, the query method of the Apollo Client accepts some generic parameters:

Also, notice that we pass an object to the query method, containing the following:

In our case, we have a single variable to provide: the search term, which we have received from our Search component when the search operation was triggered.

In the case of the query to find songs and assuming that the happy path is followed, when the response is received, our .then function will be called with the result of the query. In this case, it should receive an object of the ApolloQueryResult<FindSongs> type.

Finally, within the callback, we extract the data property of the query result and pass the Song objects that it contains to our updateFoundSongs state update function to update our component accordingly.

The query to retrieve artists works in the exact same way.

From that point on, our template can use the updated state. Since we have used the state update function (which is mandatory!), then our component and its children get re-rendered.

If you want to give it a try right away, then you can replace the code right after the searchInputCleared function with the following:

const foundArtistsList = foundArtists.map(item => <li key={item.id}>{item.name}</li>); 
const foundSongsList = foundSongs 
    .filter(item => item.hasLyrics) 
    .map(item => <li key={item.id}>{item.name}</li>); 
 
return ( 
    <Container className='lf-home'> 
        <Search searchCleared={searchInputCleared} searchTriggered=
{search}/> <ul> {foundArtistsList} </ul> <ul> {foundSongsList} </ul> </Container> );

And import Container from react-bootstrap:

import { Container } from 'react-bootstrap';

Here, we have constructed a list of li HTML elements by looping over the list of found artists as well as another one for the list of songs. We then simply render those insides of a ul element within the template.

In the case of the songs list, we have added a filter to ignore songs for which no lyrics are available. In a real-world scenario, you should directly filter the result set in the backend (to avoid unnecessary network traffic).

Alternatively, you could instead adapt the user interface to either visually make the distinction between songs that do have lyrics and those that don't, or provide a toggle to show/hide songs without lyrics. These are important UI/UX considerations but this is out of the scope of this book.

At this point, you have seen the whole process, from defining the backend GraphQL type using a code-first approach to writing queries, generating code, sending actual queries from the client-side, and handling the results.

This indeed eludes many important design questions, such as what happens when the backend is unavailable or how to deal with errors in general, but it at least gives you a good overview of how you can create and use a GraphQL API to integrate a frontend application to a backend system.

We won't go any further with GraphQL and Apollo here, but rest assured that there is a whole lot more to learn. For instance, here we have only performed read operations, but GraphQL also supports modifications through mutations.

The React integration of Apollo also allows us to write queries directly as part of the JSX code, using the <Query> element. We won't cover it here as we have chosen a more generic/programmatic approach, but in real-world applications, you would certainly prefer to use the React way. You can find more information about that here: https://www.apollographql.com/docs/react/integrations and here: https://medium.com/open-graphql/react-hooks-for-graphql-3fa8ebdd6c62. As you'll see, there are equivalent integrations for Angular and Vue.

GraphQL APIs are getting really popular lately, so make sure to check out the following references if you want to continue learning about those.

Check out these references: