GraphQL’s adoption is increasing, and for good reason. Many believe it’s the future for APIs, and big companies (GitHub, Coursera, PayPal, Shopify) using it just sparks the developer interest even further.
It’s really an exciting time to learn GraphQL, especially for React Native developers because the apps that we create usually communicate with a server. GraphQL makes it easier for us to get the specific data we want with less code. Gone are the days where we have to hit multiple endpoints just to get the data we want from a REST API.
In this tutorial, you will learn how to implement a simple GraphQL server and query the data from a React Native app.
Basic knowledge of React Native and Node.js is required to follow this tutorial.
The following package versions are used in this tutorial:
If you encounter any issues getting the app to work, try using the above versions instead.
We will build a Pokemon viewer app. This will pick a random Pokemon and display it on the screen. When the user taps on it, a new Pokemon will be fetched. It’s really simple but it’s still going to demonstrate how to implement the full GraphQL stack.
You can view the source code of the app on this GitHub repo.
To ensure compatibility, clone the GitHub repo for this project:
git clone https://github.com/anchetaWern/RNGraphQL
This contains the package.json
file for both the server and the React Native app. The starter
branch contains the starting point for this tutorial:
git checkout starter
Once you’ve switched branches, you can go ahead and install the dependencies and re-create the android
and ios
folders for the app:
1yarn 2 react-native eject
That will install all the GraphQL-related packages that we’re going to need in the app. Later on, you’ll learn what each one does once we proceed to code the client side.
Lastly, we also need to install the server dependencies:
1cd server 2 yarn
In this section, we will be creating the GraphQL server using Apollo. As you might already know, GraphQL is only a specification, not an implementation. That’s why we need something like Apollo to implement it.
Apollo is a platform which provides us with all the tools needed for implementing a GraphQL server. This includes both the server and the client. The server component is used for defining the schema and resolvers (provides instructions on how to turn a specific GraphQL operation into data). On the other hand, the client component is responsible for providing tools that we can use for querying the server and binding the results to the UI.
The first step in creating a GraphQL server is for us to define the schema. This is where you strictly define what data your API is managing. In this case, we’re handling basic Pokemon data.
If you’ve seen the demo gif earlier, you know exactly which Pokemon data we’re working with:
But aside from that, we also need to define the queries that the server is going to handle. In this case, we’re using the PokéAPI as our direct data source so our queries will be limited by the endpoints they’re exposing. We’re only going to need a few data from these endpoints so it’s fine if we use them directly:
/pokemon/{id}
- used for getting the Pokemon data./pokemon-species/{id}
- used for getting the description text.With that in mind, we know that we can only have queries which accept the ID of the Pokemon as its argument.
NOTE: We’re not really going to cover caching in this tutorial, but it’s important to implement it if you’re going to use an existing REST API as a direct data source. That way, you won’t get throttled from using the API too much. PokéAPI is actually limited to 100 API requests per IP address per minute.
Now we’re ready to define the schema. Create a server/schema.js
file and add the following code:
1const { gql } = require('apollo-server'); 2 3 const typeDefs = gql` 4 type Query { 5 pokemon(id: ID!): Pokemon 6 } 7 8 type Pokemon { 9 id: ID! 10 name: String 11 desc: String 12 pic: String 13 types: [PokemonType!]! 14 } 15 16 type PokemonType { 17 id: Int! 18 name: String! 19 } 20 `; 21 22 module.exports = typeDefs;
Breaking down the above code, we first imported the apollo-server
package. This allows us to define the GraphQL schema. The schema includes the shape of the data we expect to return, as well as the shape of the queries.
Let’s first start with the Query
type which describes what data we can fetch. In this case, we only want the user to fetch Pokemon data so the query is named pokemon
. To specify arguments, you need to put them inside the parentheses right after the name of the query. In this case, we want the users to supply the ID of the Pokemon. An exclamation is added after it to indicate that it is a required argument. After that, we specify the return data after the colon (Pokemon
). This is a custom GraphQL object type which we’ll define next:
1type Query { 2 pokemon(id: ID!): Pokemon 3 }
Next, we need to define the Pokemon
object type. This is where we specify all the properties (and their data type) that are available for this particular type. Most of the properties that we need to expose are only scalar types (ID
and String
):
ID
is a unique identifier for a specific object of this type. An exclamation is added after it which means it cannot have a value of null
.String
is just your usual primitive type for storing string values.PokemonType
is yet another object type. The types
property is used for storing an array of objects so we also have to break down the object to its expected properties:1type Pokemon { 2 id: ID! 3 name: String 4 desc: String 5 pic: String 6 types: [PokemonType] 7 }
Here’s the definition of the PokemonType
:
1type PokemonType { 2 id: Int 3 name: String 4 }
You can find more types on this cheat sheet.
Now that we’ve defined our schema, the next step is to connect to a data source. Apollo comes with a data source API, which you could use to easily connect to an existing database or a REST API. As mentioned earlier, we will be using PokéAPI as our data source. This API provides Pokemon data from all of the Pokemon games so it’s perfect for our needs.
Create a server/datasources/poke.js
file and add the following:
1const { RESTDataSource } = require('apollo-datasource-rest'); 2 3 class PokeAPI extends RESTDataSource { 4 constructor() { 5 super(); 6 this.baseURL = 'https://pokeapi.co/api/v2/'; 7 } 8 9 pokemonReducer(pokemon, pokemonSpecies) { 10 return { 11 id: pokemon.id || 0, 12 name: pokemon.name, 13 desc: this.getDescription(pokemonSpecies.flavor_text_entries), 14 pic: pokemon.sprites.front_default, // image URL of the front facing Pokemon 15 types: this.getTypes(pokemon.types) 16 }; 17 } 18 19 getDescription(entries) { 20 return entries.find(item => item.language.name === 'en').flavor_text; 21 } 22 23 getTypes(types) { 24 return types.map(({ slot, type }) => { 25 return { 26 "id": slot, // the type's index 27 "name": type.name // the type's name (e.g. electric, leaf) 28 } 29 }); 30 } 31 32 async getPokemonById({ id }) { 33 const pokemonResponse = await this.get(`pokemon/${id}`); 34 const pokemonSpeciesResponse = await this.get(`pokemon-species/${id}`); 35 return this.pokemonReducer(pokemonResponse, pokemonSpeciesResponse); 36 } 37 } 38 39 module.exports = PokeAPI;
If you’ve consumed any sort of REST API before (and I assume you have), the code above should easily make sense to you. The apollo-datasource-rest
package really makes this simple. This package exposes the RESTDataSource
class which allows us to make a request to a REST API:
1const pokemonResponse = await this.get(`pokemon/${id}`); // this.post for POST requests 2 const pokemonSpeciesResponse = await this.get(`pokemon-species/${id}`);
From there, all we had to do was to extract the data that we need. The only thing you need to remember is that the data you’re extracting should correspond to the properties that you’ve defined earlier in your schema: id
, name
, desc
, pic
, and types
:
1pokemonReducer(pokemon, pokemonSpecies) { 2 return { 3 id: pokemon.id || 0, 4 name: pokemon.name, 5 desc: this.getDescription(pokemonSpecies.flavor_text_entries), 6 pic: pokemon.sprites.front_default, // image URL of the front facing Pokemon 7 types: this.getTypes(pokemon.types) 8 }; 9 }
The final piece of the puzzle is the resolvers. The resolvers allow us to define the mapping of the queries you’ve defined in your schema to the data source method which returns the data. It follows the format:
fieldName: (parent, args, context, info) => data;
Here’s what each one does:
parent
- an object that contains the result returned from the resolver on the parent type. This is always blank (_
) because it refers to the root of the graph.args
- an object containing the arguments passed to the field. In this case, our query only accepts the id
of the Pokemon. We then need to pass the id
to the data source method (getPokemonById()
) as an object property because that’s how we defined it earlier on the server/datasources/poke.js
file.context
- this is where we can access our data sources. We can extract the data source from the dataSources
property.info
- an object which contains information about the execution state of the operation. We don’t really need to use it in this case so we just won’t supply it at all.To define the resolver, create a server/resolvers.js
file and add the following:
1module.exports = { 2 Query: { 3 pokemon: (_, { id }, { dataSources }) => 4 dataSources.pokeAPI.getPokemonById({ id }) 5 } 6 };
The final step in implementing the GraphQL server is to bring everything together. Create a server/index.js
file and add the following:
1const { ApolloServer } = require('apollo-server'); 2 const typeDefs = require('./schema'); 3 const resolvers = require('./resolvers'); 4 5 const PokeAPI = require('./datasources/poke'); 6 7 const server = new ApolloServer({ 8 typeDefs, 9 resolvers, 10 dataSources: () => ({ 11 pokeAPI: new PokeAPI() 12 }) 13 }); 14 15 server.listen().then(({ url }) => { 16 console.log(`GraphQL Server is running at ${url}`); 17 });
In the above code, we import the ApolloServer
class from the apollo-server
package. This allows us to fire up an Apollo server which accepts an object containing the schema, resolvers, and data sources we’ve defined earlier.
At this point, you can now run the server:
1cd server 2 yarn start
You can play around with it by accessing http://localhost:4000/
on your browser.
If you get a blank screen, delete the node_modules
folder and yarn.lock
file. After that, remove the dependencies
property in the package.json
file and re-install the dependencies with the following command:
yarn add apollo-datasource-rest@0.4.0 apollo-server@2.5.0 graphql@14.3.0
From there, you can view the schema that we’ve defined earlier by clicking on the SCHEMA tab located on the right part of the screen:
But this interface is mostly used for testing out your queries. In this case, we want to fetch the Pokemon with the ID of 25:
1query GetPokemonByName { 2 pokemon(id: 25) { 3 id, 4 name, 5 desc, 6 pic 7 } 8 }
That will return the following result:
1{ 2 "data": { 3 "pokemon": { 4 "id": "25", 5 "name": "pikachu", 6 "desc": "Its nature is to store up electricity. Forests\nwhere nests of Pikachu live are dangerous,\nsince the trees are so often struck by lightning.", 7 "pic": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png" 8 } 9 } 10 }
What if we also want to get the types of the Pokemon? Intuitively, we would just add types
to the properties we’ve specified:
1query GetPokemonByName { 2 pokemon(id: 25) { 3 id, 4 name, 5 desc, 6 pic, 7 types 8 } 9 }
But that’s going to return an error:
The error says:
The field types of type [PokemonType] must have a selection of subfields.
This means that you also need to specify the subfields that you want to fetch like so:
1query GetPokemonByName { 2 pokemon(id: 25) { 3 id, 4 name, 5 desc, 6 pic, 7 types { 8 id, 9 name 10 } 11 } 12 }
That will return the following:
1{ 2 "data": { 3 "pokemon": { 4 "id": "25", 5 "name": "pikachu", 6 "desc": "Its nature is to store up electricity. Forests\nwhere nests of Pikachu live are dangerous,\nsince the trees are so often struck by lightning.", 7 "pic": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png", 8 "types": [ 9 { 10 "id": 1, 11 "name": "electric" 12 } 13 ] 14 } 15 } 16 }
Now that we’re done implementing the server, it’s time for us to add the code for the React Native app. Open the App.js
file and add the following:
1import React, { Component } from "react"; 2 import { View, Text, ActivityIndicator } from "react-native"; 3 4 import { ApolloProvider, Query } from "react-apollo"; 5 import ApolloClient from "apollo-boost"; 6 import gql from "graphql-tag";
Here’s what each package does:
apollo-boost
- used for connecting to an Apollo server.react-apollo
- provides React components that allows us to fetch data from the Apollo server.graphql-tag
- used for parsing GraphQL queries into a format understood by the Apollo client.Next, connect to the server that we created earlier. Replace the placeholder with your machine’s internal IP address. Be sure that your machine and your device are connected to the same network when you do so. You can also use ngrok if you want:
const client = new ApolloClient({ uri: 'http://YOUR_INTERNAL_IP_ADDRESS:4000/graphql' })
Next, import the component for rendering the Pokemon data as well as the helper function for generating random integers:
1import Pokemon from "./src/components/Pokemon"; 2 import getRandomInt from "./src/helpers/getRandomInt";
Next, we’re going to use the Context API so we can pass the Pokemon data through the component tree without having to pass props needed by the Pokemon
component down manually at every level:
export const AppContext = React.createContext({ data: { pokemon: null } });
Next, create the component and declare its default state. The query
contains the same query that we used earlier. We’re putting it in the state so the component will re-render every time we update it:
1export default class App extends Component { 2 3 state = { 4 query: null 5 } 6 7 // next: add componentDidMount 8 }
Once the component is mounted, we generate the query and update the state:
1componentDidMount() { 2 const query = this.getQuery(); 3 this.setState({ 4 query 5 }); 6 }
Here’s the getQuery()
function. This generates a random ID and uses it for the query:
1getQuery = () => { 2 const randomID = getRandomInt(1, 807); 3 return ` 4 query GetPokemonById { 5 pokemon(id: ${randomID}) { 6 id, 7 name, 8 desc, 9 pic, 10 types { 11 id, 12 name 13 } 14 } 15 } 16 ` 17 }
In the render()
method, the <ApolloProvider>
component is where we pass the client we created with the ApolloClient
earlier. Then we add the <Query>
component as its child, this is where you pass the query
. Don’t forget to parse the query
with the gql
module. By default, loading
will have a value of true
. In that case, we show a loading animation. Once the server responds, it can either be an error
or data
. The data
contains the same data you saw earlier when we tried running some queries. From there, we just pass the Pokemon data and the function for fetching a new one to the app’s context:
1render() { 2 const { query } = this.state; 3 if (!query) return null; 4 5 return ( 6 <ApolloProvider client={client}> 7 <Query query={gql`${query}`} > 8 {({ loading, error, data }) => { 9 if (loading || error) return <ActivityIndicator size="large" color="#0000ff" /> 10 return ( 11 <AppContext.Provider value={{...data.pokemon, onPress: this.onGetNewPokemon}} style={styles.container}> 12 <Pokemon /> 13 </AppContext.Provider> 14 ) 15 }} 16 </Query> 17 </ApolloProvider> 18 ); 19 }
Here’s the onGetNewPokemon()
function:
1onGetNewPokemon = () => { 2 const query = this.getQuery(); 3 this.setState({ 4 query 5 }); 6 }
Here’s the helper for generating random integers. Create the src/helpers
folder to house it:
1// src/helpers/getRandomInt.js 2 const getRandomInt = (min, max) => { 3 min = Math.ceil(min); 4 max = Math.floor(max); 5 return Math.floor(Math.random() * (max - min + 1)) + min; 6 } 7 8 export default getRandomInt;
The Pokemon
component is used for displaying the Pokemon data. Create a src/components
folder to house it.
Start by importing the packages we need:
1// src/components/Pokemon.js 2 import React from 'react'; 3 import { View, Text, Image, FlatList, TouchableOpacity } from 'react-native'; 4 import { AppContext } from '../../App'; // import the context we exported earlier on the App.js file
Next, render the component. Wrap it in <AppContext.Consumer>
so you get access to the data and the onPress
function that we passed to the context earlier. From there, it’s just a matter of displaying the data using the right components:
1const Pokemon = () => { 2 return ( 3 <AppContext.Consumer> 4 { 5 ({ name, pic, types, desc, onPress }) => 6 <TouchableOpacity onPress={onPress}> 7 <View style={styles.mainDetails}> 8 <Image 9 source={{uri: pic}} 10 style={styles.image} resizeMode={"contain"} /> 11 <Text style={styles.mainText}>{name}</Text> 12 13 <FlatList 14 columnWrapperStyle={styles.types} 15 data={types} 16 numColumns={2} 17 keyExtractor={(item) => item.id.toString()} 18 renderItem={({item}) => { 19 return ( 20 <View style={[styles[item.name], styles.type]}> 21 <Text style={styles.typeText}>{item.name}</Text> 22 </View> 23 ) 24 }} 25 /> 26 27 <View style={styles.description}> 28 <Text>{desc}</Text> 29 </View> 30 </View> 31 </TouchableOpacity> 32 } 33 </AppContext.Consumer> 34 ); 35 }
Lastly, add the styles and export the component. Most of these are just used to change the background color of the types container based on the Pokemon’s type:
1const styles = { 2 mainDetails: { 3 padding: 30, 4 alignItems: 'center' 5 }, 6 image: { 7 width: 100, 8 height: 100 9 }, 10 mainText: { 11 fontSize: 25, 12 fontWeight: 'bold', 13 textAlign: 'center' 14 }, 15 description: { 16 marginTop: 20 17 }, 18 types: { 19 flexDirection: 'row', 20 marginTop: 20 21 }, 22 type: { 23 padding: 5, 24 width: 100, 25 alignItems: 'center' 26 }, 27 typeText: { 28 color: '#fff', 29 }, 30 normal: { 31 backgroundColor: '#8a8a59' 32 }, 33 fire: { 34 backgroundColor: '#f08030' 35 }, 36 water: { 37 backgroundColor: '#6890f0' 38 }, 39 electric: { 40 backgroundColor: '#f8d030' 41 }, 42 grass: { 43 backgroundColor: '#78c850' 44 }, 45 ice: { 46 backgroundColor: '#98d8d8' 47 }, 48 fighting: { 49 backgroundColor: '#c03028' 50 }, 51 poison: { 52 backgroundColor: '#a040a0' 53 }, 54 ground: { 55 backgroundColor: '#e0c068' 56 }, 57 flying: { 58 backgroundColor: '#a890f0' 59 }, 60 psychic: { 61 backgroundColor: '#f85888' 62 }, 63 bug: { 64 backgroundColor: '#a8b820' 65 }, 66 rock: { 67 backgroundColor: '#b8a038' 68 }, 69 ghost: { 70 backgroundColor: '#705898' 71 }, 72 dragon: { 73 backgroundColor: '#7038f8' 74 }, 75 dark: { 76 backgroundColor: '#705848' 77 }, 78 steel: { 79 backgroundColor: '#b8b8d0' 80 }, 81 fairy: { 82 backgroundColor: '#e898e8' 83 } 84 } 85 86 export default Pokemon;
At this point, you can now run the app:
1react-native run-android 2 react-native run-ios
That’s it! In this tutorial, you learned the basics of using GraphQL in a React Native app. Specifically, you learned how to set up your own GraphQL server using Apollo, use an existing REST API as its data source, consume the data from a React Native app, and display it to the user.
Even though we’ve implemented the full stack on this tutorial, there’s still a lot more to learn. Be sure to check out the GraphQL and Apollo docs to learn more.
You can view the source code of the app on this GitHub repo.