Learn How to Use an API with REST and GraphQL

Uci Lasmana
10 min readJan 3, 2024

--

In this article, I will show you how to use APIs with REST and GraphQL, and explain the difference between both of them.

If you are still new to APIs, you can read this article, “A Guide to APIs (Collection of Questions and Answers for Beginners)”.

For this tutorial, we will build a Pokemon List app using PokéAPI, which is a web service that provides detailed information about Pokemon, such as abilities, types, moves, items, and more. This is how the Pokemon app will look:

PokemonList UI

The reason I chose the PokéAPI is because its supports for both REST and GraphQL. We can use either REST or GraphQL to query the Pokemon data, so we can have a better look at how different each approach is.

However, before we build this Pokemon app, let’s first explore the key differences between REST and GraphQL.

REST vs GraphQL

From the ‘What’s the Difference Between GraphQL and REST?’ article, REST (Representational State Transfer) and GraphQL (Graph Query Language) implement several common API architectural principles. For example, here are principles they share:

  • REST and GraphQL are both API architectures that enable the exchange of data between different services or applications in a client-server model (requests from a single client result in replies from a single server).
  • REST and GraphQL are stateless, which means that each request contains all the information needed to process it, and the server does not store any session data.
  • GraphQL and REST typically are HTTP-based, as HTTP is the underlying communication protocol.

However, REST and GraphQL have different ways to access and deliver data. These are the main differences between REST and GraphQL:

When to use GraphQL vs. REST

There are some use cases we can consider before we decide which one, we are going to choose.

GraphQL is a better choice if you have these considerations:

  • You have limited bandwidth, and you want to minimize the number of requests and responses.
  • You have multiple data sources, and you want to combine them at one endpoint.
  • You have client requests that vary significantly, and you expect very different responses.

On the other hand, REST is a better choice if you have these considerations:

  • You have smaller applications with less complex data.
  • You have data and operations that all clients use similarly.
  • You have no requirements for complex data querying.

Alright, let’s build the app now!

Pokemon App

In this app we have two files, index.html and index.js. You can just copy and paste these codes below if you want.

index.html

<!DOCTYPE html>
<html class="h-full w-full">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Pokemon List</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600;700;800;900&family=Silkscreen&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
lexend: 'Lexend',
silkscreen:'Silkscreen',
},
}
},
plugins: [
function({ addUtilities }) {
const comma = {
'.comma::after': {
content: '", "',
},
'.comma:last-child::after': {
content: '""',
},
}
addUtilities(comma, ['responsive', 'hover'])
}
],
}
</script>

</head>
<body class="font-lexend bg-gray-200/75 h-full w-full p-4 border-box">
<div id="modal" class="hidden relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-6 text-center items-center ">
<div class="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all w-80">
<div class="flex pt-3 flex-col items-end">
<span class="cursor-pointer text-xl text-red-600 font-bold font-silkscreen mr-4" onclick="closeModal()">x</span>
<div class="flex flex-col self-center w-full justify-center items-center gap-4 py-2">
<h3 class="my-4 text-2xl font-bold leading-6 text-indigo-700 capitalize" id="pokemon-name"></h3>
<div class="w-full flex justify-center bg-indigo-200 p-4">
<img id="pokemon-img" src="" class="w-32 h-32" alt="">
</div>
<div class="flex flex-col text-zinc-800 divide-y divide-indigo-100 p-4 text-sm w-full">
<div class="grid grid-cols-2 place-content-center py-2">
<div>
<span>Type</span>
</div>
<div id="pokemon-type" class="capitalize flex-wrap gap-2"></div>
</div>
<div class="grid grid-cols-2 place-content-center py-2">
<div>
<span>Abilities</span>
</div>
<div id="pokemon-abilities" class="capitalize flex-wrap gap-2"></div>
</div>
<div class="grid grid-cols-2 place-content-center py-2">
<div>
<span>Moves</span>
</div>
<div id="pokemon-moves" class="capitalize flex-wrap gap-2"></div>
</div>
</div>

</div>
</div>

</div>
</div>
</div>
</div>

<div class="w-full flex justify-center">
<h1 class="rounded-lg bg-white w-fit text-center font-bold text-2xl sm:text-3xl text-indigo-600 p-2 shadow-md">
Pokemon</h1>
</div>

<div class="flex flex-col gap-4 justify-center py-8 sm:px-6">
<div>
<h3 class="text-zinc-600 font-semibold">REST</h3>
<div class="p-2 min-[380px]:p-4 sm:p-6 grid grid-cols-2 min-[380px]:grid-cols-3 min-[580px]:grid-cols-4 sm:grid-cols-5 min-[800px]:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10 gap-4 " id="pokemonListREST">
</div>
</div>
<div>
<h3 class="text-zinc-600 font-semibold ">GraphQL</h3>
<div class="p-2 min-[380px]:p-4 sm:p-6 grid grid-cols-2 min-[380px]:grid-cols-3 min-[580px]:grid-cols-4 sm:grid-cols-5 min-[800px]:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10 gap-4 " id="pokemonListGRAPH">
</div>
</div>

</div>
<script src="index.js"></script>
</body>
</html>

index.js

const pokemonListDivREST = document.getElementById('pokemonListREST');
const pokemonListDivGRAPH = document.getElementById('pokemonListGRAPH');
const modal = document.getElementById('modal');
const pokemonName= document.getElementById('pokemon-name');
const pokemonImg = document.getElementById('pokemon-img');
const pokemonTypes= document.getElementById('pokemon-type');
const pokemonAbilities= document.getElementById('pokemon-abilities');
const pokemonMoves= document.getElementById('pokemon-moves');

const closeModal = () =>{
modal.classList.add('hidden')
pokemonTypes.innerHTML=""
pokemonAbilities.innerHTML=""
pokemonMoves.innerHTML=""
}

const createDIV = (image, name) =>{
const pokemonDiv = document.createElement('div');
pokemonDiv.classList.add("bg-white", "h-28", "rounded-lg", "shadow-xl", "flex", "flex-col", "items-center", "cursor-pointer")
pokemonDiv.innerHTML = `
<div class="flex items-center bg-indigo-200 w-full h-20 rounded-t-lg">
<img src="${image}" class="w-full h-16" alt="${name}">
</div>
<span class="mt-1 text-sm text-center capitalize">${name}</span>`;
return pokemonDiv
}

const modalDetails=(image, name, abilities, types, moves)=>{
pokemonName.innerHTML=name
pokemonImg.src=image
types.forEach(type=>{
const span = document.createElement('span');
span.classList.add('comma')
span.innerHTML=type
pokemonTypes.appendChild(span)
})
abilities.forEach(ability=>{
const span = document.createElement('span');
span.classList.add('comma')
span.innerHTML=ability
pokemonAbilities.appendChild(span)
})
moves.slice(0, 6).forEach(move=>{
const span = document.createElement('span');
span.classList.add('comma')
span.innerHTML=move
pokemonMoves.appendChild(span)
})
}

function displayPokemonListREST(pokemonList) {
pokemonList.forEach(pokemon => {
fetch(pokemon.url)
.then(response => response.json())
.then(pokemonData => {
let pokeImg= pokemonData.sprites.front_default
let pokeName= pokemonData.name
const div = createDIV(pokeImg, pokeName)
pokemonListDivREST.appendChild(div)

div.addEventListener('click', () => {
//Modal-Details
const pokeAbilities= []
const pokeMoves= []
const pokeTypes= []
pokemonData.abilities.forEach(
ability=>{
pokeAbilities.push(ability.ability.name)
})
pokemonData.moves.forEach(
move=>{
pokeMoves.push(move.move.name)
})
pokemonData.types.forEach(
type=>{
pokeTypes.push(type.type.name)
})
modalDetails(pokeImg, pokeName, pokeAbilities, pokeTypes, pokeMoves)
modal.classList.remove('hidden')
})
})
.catch(error => console.error('Error:', error));
})
}

function displayPokemonListGraph(pokemonList) {
pokemonList.forEach(pokemon => {
let pokeImg=pokemon.pokemon_v2_pokemonsprites[0].sprites.front_default
let pokeName= pokemon.name
const div = createDIV(pokeImg, pokeName)
pokemonListDivGRAPH.appendChild(div)

div.addEventListener('click', () => {
const pokeAbilities= []
const pokeMoves= []
const pokeTypes= []
pokemon.pokemon_v2_pokemonabilities.forEach(
ability=>{
pokeAbilities.push(ability.pokemon_v2_ability.name)
})
pokemon.pokemon_v2_pokemonmoves.forEach(
move=>{
pokeMoves.push(move.pokemon_v2_move.name)
})
pokemon.pokemon_v2_pokemontypes.forEach(
type=>{
pokeTypes.push(type.pokemon_v2_type.name)
})
modalDetails(pokeImg, pokeName, pokeAbilities, pokeTypes, pokeMoves)
modal.classList.remove('hidden')
})
})
}

//REST
async function fetchREST(url){
try{
const response = await fetch(url)
const data = await response.json()
displayPokemonListREST(data.results)
}
catch(error)
{
console.error('Error:', error)
}
}
fetchREST('https://pokeapi.co/api/v2/pokemon?limit=15')

//GraphQL
async function fetchGraph(url){
try{
const response = await fetch(url,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
query {
pokemon_v2_pokemon(limit: 15, offset: 15) {
id
name
pokemon_v2_pokemonmoves(limit: 6) {
pokemon_v2_move {
name
}
}
pokemon_v2_pokemontypes(limit: 3){
pokemon_v2_type {
name
}
}
pokemon_v2_pokemonabilities(limit: 3) {
pokemon_v2_ability {
name
}
}
pokemon_v2_pokemonsprites {
sprites
}
}
}
`,
}),
})
const data = await response.json()
displayPokemonListGraph(data.data.pokemon_v2_pokemon)
}
catch(error)
{
console.error('Error:', error)
}
}
fetchGraph('https://beta.pokeapi.co/graphql/v1beta')

In this index.js, we have two fetch() functions, one for REST and one for GraphQL. We use fetch() to send a network request to the URL that we passed as a parameter in the function. If you want to learn more about the fetch() method you can visit this article, “What is AJAX, XMLHttpRequest, fetch(), and Promise?

Let’s breakdown the difference between implementation of REST and GraphQL in this Pokemon app:

REST

//REST
async function fetchREST(url){
try{
const response = await fetch(url)
const data = await response.json()
displayPokemonListREST(data.results)
}
catch(error)
{
console.error('Error:', error)
}
}
fetchREST('https://pokeapi.co/api/v2/pokemon?limit=15')
  • We have fetchREST() as an async function that takes a URL as an argument. In this function we used try/catch block to handle errors that might occur during the execution.
  • Inside the try block, we make a network request using fetch to “https://pokeapi.co/api/v2/pokemon?limit=15”. This URL is an API endpoint of REST PokeAPI. The limit=15 query parameter limits the response to 15 Pokemon. We will get all of available data from the 15 Pokemon.

In REST, we can’t choose what kind of data we want to retrieve, we will get all the data provided by the endpoint.

  • We use await when we fetch() the data, this means we pause execution of the function until the promise resolves. This ensures that the data is available before we proceed to the next line.
  • In this line, const data = await response.json(), we’re waiting for the Promise to resolve with the JSON response. Here, we use the .json() method to parse the response, converting it from JSON into a JavaScript object.
  • After the JSON conversion is completed, we call the displayPokemonListREST function with the results property of the data object. The results property contains an array of Pokémon data.
  • The displayPokemonListREST function is used to display a list of Pokemon and show detailed information about each Pokemon in a modal when clicked.
  • If any error occurs during the fetch operation, the catch block catches the errors and logs them to the console.

GraphQL

async function fetchGraph(url){
try{
const response = await fetch(url,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
query {
pokemon_v2_pokemon(limit: 15, offset: 15) {
id
name
pokemon_v2_pokemonmoves(limit: 6) {
pokemon_v2_move {
name
}
}
pokemon_v2_pokemontypes(limit: 3){
pokemon_v2_type {
name
}
}
pokemon_v2_pokemonabilities(limit: 3) {
pokemon_v2_ability {
name
}
}
pokemon_v2_pokemonsprites {
sprites
}
}
}
`,
}),
})
const data = await response.json()
displayPokemonListGraph(data.data.pokemon_v2_pokemon)
}
catch(error)
{
console.error('Error:', error)
}
}
fetchGraph('https://beta.pokeapi.co/graphql/v1beta')
  • We have fetchGraph() as an async function that takes a URL, “https://beta.pokeapi.co/graphql/v1beta” as an argument. In this function we used try/catch block to handle errors that might occur during the execution.
  • Inside the try block, we use await when we making a network request using fetch to sends a POST request to the Pokemon GraphQL API endpoint. await ensures that the data is available before we proceed to the next line.
  • method: 'POST', This specifies that the HTTP method of the request is POST. The PokeAPI for GraphQL only allow for POST request.
  • headers: { 'Content-Type': 'application/json' }: This sets the content type of the request to JSON.
  • body: JSON.stringify({...}): This converts the JavaScript object into a JSON string and sets it as the body of the request. The object contains a query property, which is a string that represents a GraphQL query.

A standard GraphQL POST request should use the application/json content type, and include a JSON-encoded body.

You can find more about this here, “GraphQL-Serving over HTTP

  • The GraphQL query requests a list of Pokemon (pokemon_v2_pokemon) with a limit of 15 and an offset of 15. For each Pokemon, it requests the id, name, up to 6 moves (pokemon_v2_pokemonmoves), up to 3 types (pokemon_v2_pokemontypes), up to 3 abilities (pokemon_v2_pokemonabilities), and the sprites (pokemon_v2_pokemonsprites).

With GraphQL we can query what data and how much data we want to retrieve. Here, we can request only the id, name, up to 6 moves, up to 3 types, up to 3 abilities, and the sprites.

  • In this line, const data = await response.json(), we’re waiting for the Promise to resolve with the JSON response. Here, we use the .json() method to parse the response, converting it from JSON into a JavaScript object.
  • After the JSON conversion is completed, we call the displayPokemonListGraph function with the results property of the data object. The results property contains an array of Pokémon data.
  • Just like the displayPokemonListREST function, the displayPokemonListGraph function is also used to display a list of Pokemon and show detailed information about each Pokemon in a modal when clicked.
  • .catch(error => console.error('Error:', error)), this line catches any errors that occur during the fetch operation and logs them to the console.

limit and offset are used to control the amount of data that is returned and to implement pagination.

Limit is a parameter that specifies the maximum number of records to return. For example, limit: 15 would return a maximum of 15 records.

Offset is a parameter that specifies the number of records to skip before starting to return records. For example, offset: 15 would skip the first 15 records and then start returning records from the 16th record onwards.

But if we have a “Next” button to go to the next page, the offset will increase by 15 each time the “Next” button is clicked. For example, if we set the offset to 15, it will skip the first 15 Pokemon and start from the 16th. So, in the second page, the offset is 30, which means it will start from the 31st Pokémon.

In summary, while REST and GraphQL achieve same results (retrieving and displaying data about Pokemon), they use different techniques.

The REST approach makes multiple requests and receives full Pokemon objects, while the GraphQL approach makes a single request and receives only the requested data.

With GraphQL we can query what data and how much data we want to retrieve. This is really different from REST, where we can’t choose what kind of data we want to retrieve, we will get all the data provided by the endpoint.

This can make the GraphQL approach more efficient, especially for complex or large-scale applications. However, the best approach to use depends on the specific requirements and constraints of your project.

Finally, we have finished this tutorial!

You can check out this Pokemon app by visiting this link, Pokemon Lists (ucilasmana.github.io). You can also get the full code from this repository, ucilasmana/pokemonLists.

I hope this can be helpful for you and have a good day!

--

--

Uci Lasmana

Someone who enjoys every journey in developing ideas, passionate about solving problems without ignoring the beauty of the visuals.