{{announcement.body}}
{{announcement.title}}

How to Build a Pokedex React App with a Slash GraphQL Backend

DZone 's Guide to

How to Build a Pokedex React App with a Slash GraphQL Backend

Take a look at how you can use use GraphQL to create a demo application of a Pokemon Pokedex app with React.

· Web Dev Zone ·
Free Resource

Frontend developers want interactions with the backends of their web applications to be as painless as possible. Requesting data from the database or making updates to records stored in the database should be simple so that frontend developer can focus on what they do best: creating beautiful and intuitive user interfaces.

GraphQL makes working with databases easy. Rather than relying on backend developers to create specific API endpoints that return pre-selected data fields when querying the database, frontend developers can make simple requests to the backend and retrieve the exact data that they need—no more, no less. This level of flexibility is one reason why GraphQL is so appealing.

Even better, you can use a hosted GraphQL backend—Slash GraphQL (by Dgraph). This service is brand new and was publicly released on September 10, 2020. With Slash GraphQL, I can create a new backend endpoint, specify the schema I want for my graph database, and—voila!—be up and running in just a few steps.

The beauty of a hosted backend is that you don't need to manage your own backend infrastructure, create and manage your own database, or create API endpoints. All of that is taken care of for you.

In this article, we're going to walk through some of the basic setup for Slash GraphQL and then take a look at how I built a Pokémon Pokédex app with React and Slash GraphQL in just a few hours!

You can view all of the code here on GitHub.

Overview of the Demo App


Pokémon Pokédex app

Pokémon Pokédex app

What 90s child (or adult, for that matter) didn't dream of catching all 150 original Pokémon? Our demo app will help us keep track of our progress in becoming Pokémon masters.

As we build out our app, we'll cover all the CRUD operations for working with an API: create, read, update, and delete.

We'll start by adding all our Pokémon to the database online in Slash GraphQL's API Explorer. Then, in the Pokédex app UI, we'll display all 151 Pokémon queried from the database. (Hey, I couldn't leave out Mew, could I?) At the top of the screen, we'll show two dropdown menus that will allow us to filter the shown results by Pokémon type and by whether or not the Pokémon has been captured. Each Pokémon will also have a toggle switch next to it that will allow us to mark the Pokémon as captured or not. We won't be deleting any Pokémon from our database via the app's UI, but I'll walk you through how that could be done in the event that you need to clean up some data.

Ready to begin our journey?

Getting Started with Slash GraphQL

Creating a New Backend

Once you've created your Slash GraphQL account, you can have your GraphQL backend up and running in just a few steps:

  1. Click the "Create a Backend" button.
  2. Give it a name. (For example, I chose "pokedex".)
  3. Optionally, give the API endpoint URL a subdomain name. (Again, I chose "pokedex".)
  4. Optionally, choose a provider and a zone. (This defaults to using AWS in the US West region.)
  5. Click the "Create New Backend" button to confirm your choices.
  6. Get your backend endpoint. (Mine looks like this: https://pokedex.us-west-2.aws.cloud.dgraph.io/graphql.)
  7. Click the "Create your Schema" button.

That's it! After creating a new backend, you'll have a live GraphQL database and API endpoint ready to go.

Creating a New Backend

Creating a New Backend

Creating a Schema

Now that we have our backend up and running, we need to create the schema for the type of data we'll have in our database. For the Pokédex app, we'll have a Pokémon type and a PokémonType enum.


SQL
 




xxxxxxxxxx
1
28


 
1
enum PokemonType {
2
  Bug
3
  Dark
4
  Dragon
5
  Electric
6
  Fairy
7
  Fighting
8
  Fire
9
  Flying
10
  Ghost
11
  Grass
12
  Ground
13
  Ice
14
  Normal
15
  Poison
16
  Psychic
17
  Rock
18
  Steel
19
  Water
20
}
21
 
          
22
type Pokemon {
23
  id: Int! @search
24
  name: String! @search(by: [fulltext])
25
  captured: Boolean! @search
26
  imgUrl: String!
27
  pokemonTypes: [PokemonType!]! @search
28
}



There's a lot to unpack in that small amount of code! The PokémonType enum is straightforward enough—it's a set of all the Pokémon types, including Fire, Water, Grass, and Electric. The Pokémon type describes the shape of our data that we'll have for each Pokémon. Each Pokémon will have an ID, a name, an image URL for displaying the Pokémon's picture, the types of Pokémon it is, and a status indicating whether or not the Pokémon is captured.

You can see that each field has a data type associated with it. For example, id is an Int (integer), name and imgUrl are String types, and captured is a Boolean. The presence of an exclamation point ! means the field is required. Finally, adding the @search keyword makes the field searchable in your queries and mutations.

To test out working with our database and newly created schema, we can use the API Explorer, which is a neat feature that allows us to run queries and mutations against our database right from within the Slash GraphQL web console. 

Populating Our Database

Let's use the API Explorer to insert all of our Pokémon into the Pokédex database. We'll use the following mutation:

SQL
 




xxxxxxxxxx
1
21


1
mutation AddPokemon {
2
  addPokemon(input: [
3
    { id: 1, name: "Bulbasaur", captured: false, imgUrl: "http://img.pokemondb.net/artwork/bulbasaur.jpg", pokemonTypes: [Grass, Poison] },
4
    { id: 2, name: "Ivysaur", captured: false, imgUrl: "http://img.pokemondb.net/artwork/ivysaur.jpg", pokemonTypes: [Grass, Poison] },
5
    { id: 3, name: "Venusaur", captured: false, imgUrl: "http://img.pokemondb.net/artwork/venusaur.jpg", pokemonTypes: [Grass, Poison] },
6
    { id: 4, name: "Charmander", captured: false, imgUrl: "http://img.pokemondb.net/artwork/charmander.jpg", pokemonTypes: [Fire] },
7
    { id: 5, name: "Charmeleon", captured: false, imgUrl: "http://img.pokemondb.net/artwork/charmeleon.jpg", pokemonTypes: [Fire] },
8
    { id: 6, name: "Charizard", captured: false, imgUrl: "http://img.pokemondb.net/artwork/charizard.jpg", pokemonTypes: [Fire, Flying] },
9
    { id: 7, name: "Squirtle", captured: false, imgUrl: "http://img.pokemondb.net/artwork/squirtle.jpg", pokemonTypes: [Water] },
10
    { id: 8, name: "Wartortle", captured: false, imgUrl: "http://img.pokemondb.net/artwork/wartortle.jpg", pokemonTypes: [Water] },
11
    { id: 9, name: "Blastoise", captured: false, imgUrl: "http://img.pokemondb.net/artwork/blastoise.jpg", pokemonTypes: [Water] },
12
  ]) {
13
    pokemon {
14
      id
15
      name
16
      captured
17
      imgUrl
18
      pokemonTypes
19
    }
20
  }
21
}


For brevity I've only shown the first nine Pokémon in the snippet above. Feel free to check out the full code snippet for adding all the Pokémon.


Adding all the Pokémon via the API Explorer

Adding all the Pokémon via the API Explorer

 

Now, for a quick sanity check, we can query our database to make sure that all our Pokémon have been added correctly. We'll request the data for all our Pokémon like so:

SQL
 




xxxxxxxxxx
1


 
1
query GetAllPokemon {
2
  queryPokemon {
3
    id
4
    name
5
    captured
6
    imgUrl
7
    pokemonTypes
8
  }
9
}



Here's what it looks like in the API Explorer:

Querying for all Pokémon in the API Explorer

Querying for all Pokémon in the API Explorer


We could also write a similar query that only returns the Pokémon names if that's all the data we need. Behold, the beauty of GraphQL!


SQL
 




xxxxxxxxxx
1


 
1
query GetAllPokemonNames {
2
  queryPokemon {
3
    name
4
  }
5
}




Querying for All Pokémon Names in the API Explorer

Querying for All Pokémon Names in the API Explorer


Fetching Data in the App

Now that we've added our Pokémon to the Pokédex and verified the data is in fact there, let's get it to show up in our app. Our app was built with React and Material UI for the frontend and was bootstrapped using create-react-app. We won't be going through step-by-step how to build the app, but we'll highlight some of the key parts. Again, the full code is available on GitHub if you'd like to clone the repo or just take a look.

When using Slash GraphQL in our frontend code, we essentially just make a POST request to our single API endpoint that we were provided when creating the backend. In the body of the request, we provide our GraphQL code as the query, we write a descriptive name for the query or mutation as the operationName, and then we optionally provide an object of any variables we reference in our GraphQL code.

Here's a simplified version of how we follow this pattern to fetch our Pokémon in the app:

JavaScript
 




xxxxxxxxxx
1
52


 
1
// Main generic GraphQL request
2
async function fetchGraphQL(operationsDoc, operationName, variables) {
3
  const result = await fetch(
4
    'https://pokedex.us-west-2.aws.cloud.dgraph.io/graphql',
5
    {
6
      method: 'POST',
7
      headers: {
8
        'Content-Type': 'application/json',
9
      },
10
      body: JSON.stringify({
11
        query: operationsDoc,
12
        operationName,
13
        variables,
14
      }),
15
    }
16
  )
17
 
          
18
  return await result.json()
19
}
20
 
          
21
// Fetch all Pokemon - GraphQL
22
const fetchAllPokemonOperationsDoc = `
23
  query fetchAllPokemon {
24
    queryPokemon {
25
      id
26
      name
27
      captured
28
      imgUrl
29
      pokemonTypes
30
    }
31
  }
32
`
33
 
          
34
// Fetch all Pokemon - Function
35
function fetchAllPokemon() {
36
  return fetchGraphQL(fetchAllPokemonOperationsDoc, 'fetchAllPokemon', {})
37
}
38
 
          
39
// The rest of the following code is called in the main App component:
40
 
          
41
const { errors, data } = await fetchAllPokemon()
42
 
          
43
if (errors) {
44
  console.error(errors)
45
}
46
 
          
47
const result = data.queryPokemon.sort(
48
  (pokemonA, pokemonB) => pokemonA.id - pokemonB.id
49
)
50
 
          
51
// setPokedexData is a setter method from using the `useState` React hook, not shown in this gist
52
setPokedexData(result)


We then take that data and loop over it using the Array map helper function to display each Pokémon in the UI.

The filters at the top of the page are hooked up to our API as well. When the filter values change, a new API request kicks off, but this time with a narrower set of search results. For example, here are all the Fire type Pokémon that we've captured:

Captured Fire-type Pokémon

Captured Fire-type Pokémon

The JavaScript for making an API request for Pokémon filtered by type and captured status looks a little like this:

JavaScript
 




xxxxxxxxxx
1
28


 
1
const fetchPokemonOfCertainTypeAndByCapturedStatusOperationsDoc = ({
2
  pokemonType,
3
  isCaptured,
4
}) => `
5
  query fetchPokemonOfCertainTypeAndByCapturedStatus {
6
    queryPokemon(filter: { captured: ${isCaptured}, pokemonTypes: { eq: [${pokemonType}] } }) {
7
      id
8
      name
9
      captured
10
      imgUrl
11
      pokemonTypes
12
    }
13
  }
14
`
15
 
          
16
function fetchPokemonOfCertainTypeAndByCapturedStatus({
17
  pokemonType,
18
  isCaptured,
19
}) {
20
  return fetchGraphQL(
21
    fetchPokemonOfCertainTypeAndByCapturedStatusOperationsDoc({
22
      pokemonType,
23
      isCaptured,
24
    }),
25
    'fetchPokemonOfCertainTypeAndByCapturedStatus',
26
    {}
27
  )
28
}


 

Updating Data in the App

At this point we've sufficiently covered creating Pokémon from the API Explorer and fetching Pokémon within our Pokédex app via JavaScript. But what about updating Pokémon? Each Pokémon has a toggle switch that controls the Pokémon's captured status. Clicking on the toggle updates the Pokémon's captured status in the database and then updates the UI accordingly.

Here is our JavaScript to update a Pokémon:

JavaScript
 




xxxxxxxxxx
1
26


 
1
// Update the Pokemon Captured Status - GraphQL
2
const updatePokemonCapturedStatusOperationsDoc = (
3
  pokemonId,
4
  newIsCapturedValue
5
) => `
6
  mutation updatePokemonCapturedStatus {
7
    updatePokemon(input: {filter: {id: {eq: ${pokemonId}}}, set: {captured: ${newIsCapturedValue}}}) {
8
      pokemon {
9
        id
10
        name
11
        captured
12
        imgUrl
13
        pokemonTypes
14
      }
15
    }
16
  }
17
`
18
 
          
19
// Update the Pokemon Captured Status - Function
20
export function updatePokemonCapturedStatus(pokemonId, newIsCapturedValue) {
21
  return fetchGraphQL(
22
    updatePokemonCapturedStatusOperationsDoc(pokemonId, newIsCapturedValue),
23
    'updatePokemonCapturedStatus',
24
    {}
25
  )
26
}



We then call the updatePokemonCapturedStatus function when the toggle value changes. This kicks off the API request to update the value in the database. Then, we can either optimistically update the UI without waiting for a response from the backend, or we can wait for a response and merge the result for the single Pokémon into our frontend's larger dataset of all Pokémon. We could also simply request all the Pokémon again and replace our frontend's stored Pokémon info with the new result, which is what I chose to do.

Deleting Data from the Database

The last of the CRUD operations is "delete". We won't allow users to delete Pokémon from within the app's UI; however, as the app admin, we may need to delete any mistakes or unwanted data from our database. To do so, we can use the API Explorer again.

For example, if we found that we have an extra Bulbasaur in our Pokédex, we could delete all the Bulbasaurs:

SQL
 




xxxxxxxxxx
1


 
1
mutation DeletePokemon {
2
  deletePokemon(filter: { name: { alloftext: "Bulbasaur" } }) {
3
    pokemon {
4
      name
5
    }
6
  }
7
}




Deleting All Bulbasaur Pokémon Via the API Explorer

Deleting All Bulbasaur Pokémon Via the API Explorer


Then, we could add one Bulbasaur back:

SQL
 




xxxxxxxxxx
1
13


 
1
mutation AddPokemon {
2
  addPokemon(input: [
3
    { id: 1, name: "Bulbasaur", captured: false, imgUrl: "http://img.pokemondb.net/artwork/bulbasaur.jpg", pokemonTypes: [Grass, Poison] }
4
  ]) {
5
    pokemon {
6
      id
7
      name
8
      captured
9
      imgUrl
10
      pokemonTypes
11
    }
12
  }
13
}


 

Wrapping Up

So, what did we learn? By now we should understand how to work with Slash GraphQL in the context of a React app. We've covered all the CRUD operations to make a pretty sweet Pokédex app. We may have even caught a few Pokémon along the way.

Hopefully we didn't... hurt ourselves in confusion... [cue audible groans from the readers].

We haven't yet covered how to add authentication to secure our app or how to use the Apollo client when making our GraphQL requests, but those are important topics for another article!

As an experienced frontend developer but without much experience using GraphQL, working with Slash GraphQL was refreshingly easy. Getting set up was a breeze, and the API Explorer along with the documentation played a crucial role in helping me explore the various queries and mutations I could make with my data.

Slash GraphQL, I choose you! [more audible groans from the readers]

Topics:
database architecture, database design, databases, development, dgraph, graph databases, graph databases in the cloud, pokemon, reactjs, web developement

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}