Skip to content
banner

Consuming GraphQL APIs on React apps

React, GraphQL, Apollo Client9 min read

"Consuming GraphQL APIs on React apps"

Welcome, developers 🖖 It's been a while, huh? Yeah, I know. I want to apologize to everyone who follows us! The past few months have been quite busy at work and I wasn't capable of finding time for new posts, but anyway, here I am back to business finally! I could not be happier to be back to our blog, and to make it alive more than ever 🔥 I've got some pretty exciting ideas on new posts!

During this time away, I've been thinking about some strategies on how we can make the Welcome, Developer blog reaches a wider audience. The goal is simple, learn together, expand our knowledge, and contribute to each other.

With that in mind, I have decided to approach a couple of cool areas here, such as certifications, algorithms, and data structures. The plan is to make the experience fun and enjoyable through practical exercises and real scenarios, so we stay motived to achieve our goals! Stay tuned, guys 📣

Enough talking, for now, time to focus on today's subject. Let's talk about GraphQL, shall we? As I always say, grab your favorite source of caffeine and let's get our hands dirty 💪

Introduction

The definition of GraphQL is simple; it's a query language for APIs, and a server-side runtime for executing queries by using a type system you define for your data. Right, but what that mean exactly? Think about when you fetch data from a REST API to populate a model, and you end up having to request multiple endpoints to join the data, or at least, only needing a couple of fields returned in a long list of key-value pairs.

Can you imagine already where GraphQL acts? What if there is only a single API endpoint to request, and we could only ask for the data that we need? Yep, that's when our friend comes to play!

A GraphQL service is responsible for communicating with clients, and also, it's where the data fields, types, and functions are defined. It can be developed using a wide variety of languages, such as JavaScript, Go, Java, Python, and many others (choose your favorite!).

An important thing to highlight here is that GraphQL is not a database, or even tied to any specific database or storage engine.

The documentation and tutorials available on the GraphQL website are very rich and easy to follow. I highly recommend you all to have a look at it!

In my opinion, once we get used to using GraphQL APIs to fetch data to our apps, there is no turning back 🤓 It's fun to define the data schema on the server, and at the same level, to query the data on the clients. You guys are gonna see what I'm talking about in a moment!

For this post, we will consume a sample GraphQL API from a React application, using Apollo. As simply well-defined by them, Apollo is the industry-standard GraphQL implementation, providing the data graph layer that connects modern apps to the cloud.

Got your IDE or code editor already opened? Then let's start putting all this theory in place, developers 🧑‍💻

Environment Setup

First of all, Node.js is required to start. Download the current version from the official website, or using any other method of your preference such as nvm. Once it's been installed, run the npm command to ensure it's all good to start our exercise:

"npm-nodejs"

Create React App

Awesome, the next move is to create a new React application! Let's do it:

1npx create-react-app wd-graphql-react-client

Just in case, run the app to ensure there is any errors:

1cd wd-graphql-react-client
2yarn start

React logo spinning on localhost:3000? Well done, let's move on!

The objective of today is not to focus on React itself, but how to request GraphQL APIs from apps built using this JS framework. For this same reason, the post won't cover the basic details of React. However, I always want to make sure all developers can follow regardless of background knowledge.

Building Our App

Allow me to explain the theme of our app. We're going to build a currency converter app 💰 Well, not something fancy but good enough to present the currency conversion in real-time!

To get started, we will need a data source able to publicly expose the data about the currencies. We're going to use the same GraphQL API used by Apollo on their documentation, currently running and available on a CodeSandbox instance. This GraphQL server requests data in real-time from the Coinbase API.

To present the requested data, we must customize the app just created a little. I'm a fan of the Material Design, so why not use the Material-UI's components on our React app?

1yarn add @material-ui/core @material-ui/icons

Fantastic, now open the app on your IDE or code editor. This is an optional step, but feel free to remove the files that we will not use in this exercise, as highlighted below. Also, do not forget to also remove their references on the index.js (line 3) and App.js (line 1, 2) files.

"ReactApp"

Perfect, we're now ready to start coding our Currency Converter app 😎 First step, let's put some React components in place? What I have planned is:

  1. An app's top bar to show the title of our exercise;
  2. A footer to illustrate a real app;
  3. A card to represent a currency and its details;
  4. A search box to search by currency code;
  5. And, a layout component for standard purposes.

I've done the work and have already built the components to save us some time, so you and I can focus only on the purpose of this post, which is consuming GraphQL APIs!

But first, a couple of changes on the index.html file to make our pages look even better with Material-UI:

public/index.html
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="utf-8" />
6 <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
7 <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
8 <meta name="theme-color" content="#000000" />
9 <meta name="description" content="Web site created using create-react-app" />
10 <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
11 <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
13 <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
14 <title>Currency Converter App | Welcome, Developer</title>
15</head>
16
17<body>
18 <noscript>You need to enable JavaScript to run this app.</noscript>
19 <div id="root"></div>
20</body>
21
22</html>

Now, inside the src folder, please create a new folder called components. Next, create the following JSX files and copy each respective code: (remember, the goal is only to save time!)

TopBar.jsx
src/components/TopBar.jsx
1import React from "react";
2import { makeStyles } from "@material-ui/core/styles";
3import { AppBar, Toolbar, Typography } from "@material-ui/core";
4
5const useStyles = makeStyles((theme) => ({
6 root: {
7 height: '50px',
8 background: 'linear-gradient(90deg, rgba(109,78,238,1) 0%, rgba(164,36,134,1) 63%)',
9 },
10}));
11
12const TopBar = () => {
13 const classes = useStyles();
14 return (
15 <AppBar position="static" className={classes.root}>
16 <Toolbar>
17 <Typography variant="h6">
18 {`Currency Converter App | Welcome, Developer`}
19 </Typography>
20 </Toolbar>
21 </AppBar>
22 );
23};
24
25export default TopBar;
Footer.jsx
src/components/footer.jsx
1import React from "react";
2import { makeStyles } from "@material-ui/core/styles";
3import Typography from "@material-ui/core/Typography";
4
5const useStyles = makeStyles((theme) => ({
6 root: {
7 height: '50px'
8 },
9 footer: {
10 textTransform: "uppercase",
11 },
12}));
13
14const Footer = () => {
15 const classes = useStyles();
16 return (
17 <div className={classes.root}>
18 <Typography variant="body2" color="textSecondary" align="center" className={classes.footer}>
19 {"Copyright © Currency Converter - Welcome, Developer"}{" "}
20 {new Date().getFullYear()}
21 {"."}
22 </Typography>
23 </div>
24 );
25};
26
27export default Footer;
> SearchBox.jsx
src/components/SearchBox.jsx
1import React, { useState } from "react";
2import { Grid, TextField, Button } from "@material-ui/core";
3import MonetizationOnIcon from "@material-ui/icons/MonetizationOn";
4
5const SearchBox = ({ handleSubmit }) => {
6 const [currency, setCurrency] = useState(null)
7 const handleCurrency = (event) => {
8 setCurrency(event.target.value);
9 }
10 return (
11 <Grid
12 container
13 direction="row"
14 justify="flex-start"
15 alignItems="center"
16 spacing={2}
17 >
18 <Grid item>
19 <TextField id="movie-id-text" label="Currency Code" variant="outlined" autoFocus margin="dense" onChange={handleCurrency} required />
20 </Grid>
21 <Grid item>
22 <Button color="primary" variant="contained" onClick={() => handleSubmit(currency)} startIcon={<MonetizationOnIcon />}>{`Convert`}</Button>
23 </Grid>
24 </Grid>
25 )
26};
27
28export default SearchBox;
> CurrencyCard.jsx
src/components/CurrencyCard.jsx
1import React from 'react';
2import { makeStyles } from '@material-ui/core/styles';
3import { Card, CardContent, Typography } from '@material-ui/core';
4import numeral from 'numeral';
5
6const useStyles = makeStyles({
7 root: {
8 width: '250px',
9 maxHeight: '100px',
10 },
11});
12
13const CurrencyCard = (props) => {
14 const classes = useStyles();
15 return (
16 <Card className={classes.root}>
17 <CardContent>
18 <Typography variant="h5" component="h2">
19 {props.currency}
20 </Typography>
21 <Typography className={classes.pos} color="textSecondary">
22 {props.name || '(Not defined)'}
23 </Typography>
24 <Typography variant="body2" component="p">
25 {`$1 ${props.base} = ${numeral(props.rate).format('$0,0.00')} ${props.currency}`}
26 </Typography>
27 </CardContent>
28 </Card>
29 );
30}
31
32export default CurrencyCard;
> CurrencyList.jsx
src/components/CurrencyList.jsx
1import React, { useState } from "react";
2import { makeStyles } from "@material-ui/core/styles";
3import { Grid } from "@material-ui/core";
4
5// components
6import SearchBox from "./SearchBox";
7
8const useStyles = makeStyles(theme => ({
9 root: {
10 flexGrow: 1,
11 margin: theme.spacing(5)
12 }
13}))
14
15const CurrencyList = () => {
16 const classes = useStyles();
17 const [currency, setCurrency] = useState(null);
18
19 // handle get rates
20 const handleSubmit = (currency) => {
21 setCurrency(currency);
22 }
23
24 return (
25 <div className={classes.root}>
26 <Grid container spacing={3}>
27 <Grid item xs={12}>
28 <SearchBox handleSubmit={handleSubmit} />
29 </Grid>
30 <Grid item xs={12}>
31 <div>Currency data in here!</div>
32 </Grid>
33 </Grid>
34 </div >
35 )
36};
37
38export default CurrencyList;
> Layout.jsx
src/components/Layout.jsx
1import React from "react";
2import { makeStyles } from "@material-ui/core/styles";
3// components
4import Footer from "./Footer";
5import TopBar from "./TopBar";
6
7const useStyles = makeStyles(theme => ({
8 content: {
9 minHeight: 'calc(100vh - 50px - 50px)' // 50px top bar and footer height
10 }
11}))
12
13const Layout = ({ children }) => {
14 const classes = useStyles();
15 return (
16 <>
17 <TopBar />
18 <main className={classes.content}>{children}</main>
19 <Footer />
20 </>
21 );
22};
23
24export default Layout;

And last but not least, the existing App.js file:

1import React from 'react';
2import Typography from '@material-ui/core/Typography';
3
4// components
5import Layout from './components/Layout';
6import CurrencyList from './components/CurrencyList';
7
8function App() {
9 return (
10 <Layout>
11 <div style={{ marginTop: 20 }}>
12 <Typography variant="h5">{`Query currency rates in real time!`}</Typography>
13 </div>
14 <CurrencyList />
15 </Layout>
16 );
17}
18
19export default App;

Awesome, guys! We don't need to worry about layout from now on! However, if you interest in the changes we've done above, the code is quite simple. Basically, it is all about React states and Material-UI components. I encourage you to have a quick look at the React, and the Material-UI, documentations.

If our app is up and running, we can move to the next step! Is it the same as below on your side?

"Currency app"

Sweet as, guys! In the next chapter, we will learn how to fetch data from a GraphQL API!

Apollo Client

The Apollo Client is a state management library for JavaScript that enables us to consume and manage local and remote data with GraphQL. By using it, we can fetch, modify, and subscribe to data. It was designed for React applications, but also supports other frameworks such as Angular and Vue.

Using React's Hooks API, Apollo encapsulates the data management tasks of fetching data, handling cache, tracking loading states and errors, and updating the UI for you. I highly recommend you to read more about Apollo Client and dive deeper into its documentation.

Let's implement this cool library to our Currency Converter app! Btw, if you are using Visual Studio Code as your code editor, have a look at the Apollo Client extension (and color theme)!

1npm install @apollo/client graphql

Before start querying the data, it's crucial that we initialize an instance of the Apollo Client. This instance is going to receive, as properties, the configuration values about the connection with the GraphQL server, such as API address and cache settings.

For the purpose of our exercise today, a simple client setup is good enough! However, there are plenty of options available, for example, adding authentication or even customizing response logic. We can explore it a bit more later on!

First, create a new folder called ApolloClient, under src. Second, add a new file called client.js. Our Apollo Client's instance is setup as following:

src/ApolloClient/client.js
1import { ApolloClient, InMemoryCache } from '@apollo/client';
2
3export const client = new ApolloClient({
4 uri: 'https://48p1r2roz4.sse.codesandbox.io', // Coinbase GraphQL API
5 cache: new InMemoryCache()
6});

Yep, that's the only configuration we need to start! Okay, can we fetch some data, now? Hang on, not so fast...one step still pending! Do you remember that I said Apollo Client is a state management library? So, it behaves as the React Context, which we would include on top of our application. Let's do the same with Apollo Client then.

Back to our App.js file, please apply the following changes:

1import React from 'react';
2import { ApolloProvider } from '@apollo/client';
3import { client } from './ApolloClient/client';
4import Typography from '@material-ui/core/Typography';
5
6// components
7import Layout from './components/Layout';
8import CurrencyList from './components/CurrencyList';
9
10function App() {
11 return (
12 <ApolloProvider client={client}>
13 <Layout>
14 <div style={{ marginTop: 20 }}>
15 <Typography variant="h5">{`Query currency rates in real time!`}</Typography>
16 </div>
17 <CurrencyList />
18 </Layout>
19 </ApolloProvider>
20 );
21}
22
23export default App;

As you can see above, we have added the ApolloProvider high in our application so any component below it has access to the GraphQL data. The provider receives an instance of the ApolloClient to establish a connection with the GraphQL server.

Our code is ready to start requesting data! Next, we will talk about GraphQL queries.

GraphQL Queries

Apollo Client uses a React hook called useQuery to share GraphQL data with the UI. It receives a query as a parameter and returns a result object that contains loading, error, and data properties.

The query error and loading states are represented through the loading and error properties, while the result of the query is attached to the data property. It's all handled by Apollo Client!

A GraphQL query is created by passing a string in a specific format to a gql function. Let me show you an example below:

GraphQL_Example
1import { gql, useQuery } from '@apollo/client';
2
3const GET_USERS = gql`
4 query GetUsers {
5 users {
6 id
7 name
8 }
9 }
10`;

The query text has a specific format and matches the data schema predefined by the GraphQL API. To get familiar with the query format, and to explore the fields and functions available to be requested, let's use a pretty cool tool called GraphQL Playground. Open the URL of the GraphQL server we are using in this exercise (https://48p1r2roz4.sse.codesandbox.io) on your browser. In this page, you can play with the queries and learn more about the data schema! You'll notice that the same queries you run on the playground, are passed to the gql function on the React app.

Getting there, developer! Once you become familiar with the queries and schema on the playground page, move on to the CurrencyList.jsx React component on our app. According to the component structure we defined previously, this guy will be responsible for fetching the data and present it in the form of cards.

So, let's open the file and apply the following changes to it:

src/components/CurrencyList.jsx
1import React, { useState } from "react";
2import { makeStyles } from "@material-ui/core/styles";
3import { Grid } from "@material-ui/core";
4import { gql, useQuery } from "@apollo/client";
5
6// components
7import SearchBox from "./SearchBox";
8import CurrencyCard from "./CurrencyCard";
9
10const useStyles = makeStyles(theme => ({
11 root: {
12 flexGrow: 1,
13 margin: theme.spacing(5)
14 },
15 form: {
16 flex: 1,
17 display: 'flex',
18 },
19 cards: {
20 display: 'flex',
21 justifyContent: 'space-between',
22 }
23}))
24
25// GraphQL query
26const GET_CURRENCIES = gql`
27 query GetRates {
28 rates(currency: "USD") {
29 currency
30 name
31 rate
32 }
33 }`;
34
35const CurrencyList = () => {
36 const classes = useStyles();
37 const [currency, setCurrency] = useState(null);
38 const { loading, error, data } = useQuery(GET_CURRENCIES);
39
40 // handle get rates
41 const handleSubmit = (currency) => {
42 setCurrency(currency);
43 }
44
45 const handleData = (data, currency) => {
46 if (data.rates) {
47 return data.rates.map((item, e) =>
48 <Grid item>
49 <CurrencyCard key={e} base={currency} rate={item.rate} currency={item.currency} name={item.name} />
50 </Grid>)
51 } else {
52 return <div>{`No records have been found for the currency.`}</div>
53 }
54 }
55 return (
56 <div className={classes.root}>
57 <Grid container spacing={3}>
58 <Grid item xs={12}>
59 <SearchBox handleSubmit={handleSubmit} />
60 </Grid>
61 <Grid item xs={12}>
62 {/* while waiting for query response */}
63 {loading && <div>{`Loading, please wait...`}</div>}
64 {/* in case of query error */}
65 {error && <div>{`Request error.`}</div>}
66 {/* present currency list */}
67 {data && <Grid container spacing={1}>{handleData(data, currency)}</Grid>}
68 </Grid>
69 </Grid>
70 </div >
71 )
72};
73
74export default CurrencyList;

Allow me to explain the changes, guys:

  • (line 4) Imported the gql function and useQuery hook
  • (line 25-33) Defined the GraphQL with a default currency parameter
  • (line 38) Executed query and returned the properties from the result object
  • (line 45) A simple JS function to handle the data returned, and present each currency in the CurrencyCard component
  • (line 62-67) Show the content of the page based on the query response state (loading, error, data)

Now, run the app and check if you are getting the data presented properly on your page 🤞

"query-data"

Congrats, guys! We've just performed our first GraphQL query 🥳

Right, although we are getting the data from the API, it isn't behaving as expected. The ideal in our specific exercise is to request the data once the user has informed a currency code on the text field and clicked on the Convert button.

How to invoke then the gql function only an event is triggered (button click)? It's simple, we need a variation of the useQuery hook, called useLazyQuery. Let me show you next!

Lazy Query Hook

The definition of lazy queries, found on the Apollo Client documentation, couldn't be more comprehensive. So, I decided to use it here to explain the concept to you:

"The useLazyQuery hook is perfect for executing queries in response to events other than component rendering. This hook acts just like useQuery, with one key exception: when useLazyQuery is called, it does not immediately execute its associated query. Instead, it returns a function in its result tuple that you can call whenever you're ready to execute the query."

Fantastic, we only need a couple of changes to our code to start using the useLazyQuery hook. Open the CurrencyList component, and apply the following changes:

src/components/CurrencyList.jsx
1import React, { useState } from "react";
2import { makeStyles } from "@material-ui/core/styles";
3import { Grid } from "@material-ui/core";
4import { useLazyQuery, gql } from "@apollo/client";
5
6// components
7import SearchBox from "./SearchBox";
8import CurrencyCard from "./CurrencyCard";
9
10const useStyles = makeStyles(theme => ({
11 root: {
12 flexGrow: 1,
13 margin: theme.spacing(5)
14 },
15 form: {
16 flex: 1,
17 display: 'flex',
18 },
19 cards: {
20 display: 'flex',
21 justifyContent: 'space-between',
22 }
23}))
24
25/// GraphQL Query
26const GET_CURRENCIES = gql`
27 query GetRates($currency: String!) {
28 rates(currency: $currency) {
29 currency
30 rate
31 name
32 }
33 }`;
34
35const CurrencyList = () => {
36 const classes = useStyles();
37 const [currency, setCurrency] = useState(null);
38 const [getCurrencies, { loading, data, error }] = useLazyQuery(GET_CURRENCIES);
39
40 // handle get rates
41 const handleSubmit = (currency) => {
42 setCurrency(currency);
43 getCurrencies({ variables: { currency } })
44 }
45
46 const handleData = (data, currency) => {
47 if (data.rates) {
48 return data.rates.map((item, e) =>
49 <Grid item>
50 <CurrencyCard key={e} base={currency} rate={item.rate} currency={item.currency} name={item.name} />
51 </Grid>)
52 } else {
53 return <div>{`No records have been found for the currency.`}</div>
54 }
55 }
56 return (
57 <div className={classes.root}>
58 <Grid container spacing={3}>
59 <Grid item xs={12}>
60 <SearchBox handleSubmit={handleSubmit} />
61 </Grid>
62 <Grid item xs={12}>
63 {/* while waiting for query response */}
64 {loading && <div>{`Loading, please wait...`}</div>}
65 {/* in case of query error */}
66 {error && <div>{`Request error.`}</div>}
67 {/* present currency list */}
68 {data && <Grid container spacing={1}>{handleData(data, currency)}</Grid>}
69 </Grid>
70 </Grid>
71 </div >
72 )
73};
74
75export default CurrencyList;

Let me explain the changes above:

  • (line 4) Instead of useQuery, we imported useLazyQuery
  • (line 27-28) Added a variable to the query parameter
  • (line 38) Created a function to execute the query, named getCurrencies
  • (line 43) On the handleSubmit function, triggered by the button click, perform the getCurrencies functions to run the query

That's all, my friends! Try to run the app and inform any currency code of your choice. You should then see the conversion rate with other currencies:

"currency-app-cards"

Nicely done, guys. GraphQL isn't an unknown world for us anymore!

Next Steps

Pretty awesome those GraphQL API queries, huh? Yeah, I agree. I can almost hear your thoughts from here at this moment, developers. You are probably asking yourself, where is the part about mutations and subscriptions? Am I right? 🙋‍♂️

No need to worry, guys! My idea with this post was to give only an introduction to GraphQL, by learning how to fetch data from APIs on a React application. I've already planned the second part of this series, where we will learn how to create a GraphQL server as well as explore more of its concepts and capabilities 👌

I would say we have more fun together coming soon, everyone! In the meanwhile, why not to keep exploring some cool GraphQL resources, guys?

If you are stuck in any step, go to our GitHub repository and download the solution!

Useful Resources

Conclusion

Thank you so much to all of you who have gone through the exercise! It was good fun, wasn't it? GraphQL is an amazing way of handling data in modern web apps! I would be happy to see you around in the next chapters of this learning journey! I'm going to dive deeper into the concepts, build a GraphQL server in Java (or Node.js), and prepare more cool exercises so we can have some fun together! Sounds good?

I always try my best to cover all details and explain each step in the clearest way possible. I hope you have enjoyed reading this post as much as I have when preparing it ♡

I'm grateful to be back to our blog, guys! Keep up with the hard work, but do not forget to have fun along the way! Otherwise, what's the point, right? 🤗 See you in the next post, developers!

Follow Us

The Welcome, Developer has a brand new LinkedIn page! If you enjoy our content, we would be very happy to have you as a follower! In there, we will update you about new posts, new projects, and all the cool stuff!

© 2021 Welcome, Developer. All rights reserved.
Proudly made in New Zealand ♡