Learn GraphQL - From 0 to basic with ReactJs and Apollo Client (Typescript)

Requirements
The focus of this article is to cover the fundamentals of GraphQL for people with little or no experience in the subject, so it will start with a broad introduction of the fundamental concepts that will be put into practice later with Apollo Client and React, it is recommended for the practice of this article, know the following technologies:
Javascript and Typescript
React y React Router
Introduction and Motivation ✍️
Starting this year, I decided to increase my knowledge in a more constant way throughout the year, to achieve this I decided to set quarterly learning objectives and along this way, teach what I learned to those who also need it.
So the first learning objective of this 2020 was to learn GraphQL, the decision had a strong motivation based on the analysis of the StateOfJS 2020 survey, when reviewing the DataLayer GraphQL section it appeared as the main one since 2017, which makes it a Quite a strong trend if we add to that that I had only worked with API Rest, it was an almost inevitable choice to start this 2021.
If you are in a position similar to mine, wanting to expand knowledge, you do not know anything about GraphQL and you have only worked with API Rest then stay in this reading and you will learn the principles applied together with React and Apollo Client.
StateOfJS 2020 - https://2020.stateofjs.com/en-US/technologies/datalayer/
What is GraphQL?
GraphQL is a data query language, yes a query language 🤔 at first I thought it was a standard similar to REST, but no, it is a query typing language and why should that data interest us? Being a language it has a specific syntax, just as SQL is a query language for structured databases, GraphQL is a language for querying data from an API.
Some of the main benefits of using GraphQL:
It offers a better relationship between frontend and backend, both know the data types in a much clearer and previously defined way.
You can only query for the required information, in REST the endpoints return information that may not be used.
The API responses will have predictable data thanks to the definition of the queries.
It allows multiple queries in a single request.
GraphQL is agnostic for both the frontend and the backend and where the data is extracted from, it is not dependent on any specific DB or language.
To make the connection to the API we could use fetch, however now there are libraries that help us handle many additional issues not only the query or the connection, for this article we will use Apollo Client, this library offers us some benefits such as Declarative data fetching, Zero-config caching, Combine local & remote data.
Language Syntax
The language is really very similar to working with json
objects at the structure level, at the level of type of operations there are two, the first is the queries
referring to data query operations in REST would be equivalent to GET
and the other type of action It is mutuations
referring to modification or creation of the data, this is equivalent to the rest of the actions of REST POST, CREATE, UPDATE, DELETE
. Both queries
and mutations
can receive parameters to carry out their execution. Just as REST needs the BackEnd to configure each EndPoint, in GraphQL the BackEnd will be in charge of creating the definitions for each operation for both queries and mutations.
To better exemplify during this article we will use the StartWars GraphQL API, this can be found here: https://graphql.org/swapi-graphql/
Queries
To start the first thing is to use the reserved word query
, the name of this query
should be indicated and open braces{ }
, inside these is where we will put the details of the query
, in the following example we see that I use person
, this is because the API is built this way, when the BackEnd defined the action it defined that within the queries I can request person
in parentecis we can put the parameters and finally open braces again { }
inside these we will put the attributes that we want to recover, that is, we only recover the information we need, unlike REST where we retrieve information that is not always used. The word query and the name of the query are optional, however, as it is a basic article, we are going to put these examples as complete and clear as possible.
Example of query for a specific character, requesting the name
attribute:
# RequestPerson is a generic name could be any other
query RequestPerson {
# person is a query defined from the backend
# receives in parentheses the 'personID' that refers to each character
# allows us to indicate which attributes we want in this case 'name'.
person(personID: "1") {
name
}
}
Response:
{
"data": {
"person": {
"name": "Luke Skywalker"
}
}
}
From the answer we can analyze a couple of points, the answer is in json
format and additionally all the information is in the data
attribute, this is mainly by convention, it could be named different but the community defined that the data is encapsulated in this way.
Queries have the advantage that they can be composed of several queries in one, this is one of the enormous advantages.
Example of querying two different data, starship
and planet
both in the same query.
query RequestMultipleData {
starship(starshipID: 2) {
name
crew
}
planet(planetID: 1) {
name
}
}
Response:
{
"data": {
"starship": {
"name": "CR90 corvette",
"crew": "30-165"
},
"planet": {
"name": "Tatooine"
}
}
}
Another example of query, but this time consulting all the characters, and for each character we request the parameters name
, birthYear
and eyeColor
# RequestAllPeople is a generic name could be any other
query RequestAllPeople {
# allPeople is the first query defined from the backend
# It will give us the list of people
allPeople {
# people is the second query defined from the backend
# here we can indicate what attributes of each person we want
people {
name
birthYear
eyeColor
}
}
}
Response:
{
"data": {
"allPeople": {
"people": [
{
"name": "Luke Skywalker",
"birthYear": "19BBY",
"eyeColor": "blue"
},
{
"name": "C-3PO",
"birthYear": "112BBY",
"eyeColor": "yellow"
},
{
"name": "R2-D2",
"birthYear": "33BBY",
"eyeColor": "red"
}
...
]
}
}
}
Aliases
If we want to edit the name of how the json
result is returned we have a very interesting option known as Aliases, in this example we see that the result will not be shown as person
but as jedi
.
Alias query example:
# Query with Alias and additional an unnamed query as an example
query {
# <alias>: is the way to assign the alias
jedi: person(personID: "1") {
name
}
}
Response:
{
"data": {
"jedi": {
"name": "Luke Skywalker"
}
}
}
Fragments
GraphQL gives us the option to reuse the code, this is known as Fragment
, the idea is that all code that we are going to use in several queries we have in this Fragment.
Query example with Fragment
# Fragmento reusable named basicPersonFields this name is generic
fragment basicPersonFields on Person {
name
birthYear
gender
height
}
# Query using the created Fragment
query requestConFragment {
person(personID: "1") {
...basicPersonFields
homeworld {
name
}
}
}
Parameters
During all the examples I have used explicit parameters in the code, it is important that next to the name of the query the parentheses are opened and within these we indicate the variables starting with an additional $
after giving the type of this variable, In the example below we see a variable called $
id whose type is ID
by default the type ID
is defined so we can use it without problem, finally the symbol! together with the type of the parameter indicates that it is not optional, the query will not be executed if the parameter is sent.
Example of query with Parameters:
# Query with parameters
query queryConParametrosVariables($id: ID!) {
person(personID: $id) {
name
height
}
}
## $id it must be passed to the interface where the query is made
## Later I will show how to pass these variables with ReactJS and Apollo Client
Response
{
"id": 1
}
Mutations
Mutations are not at all different from queries in structure but if they have something that has been adopted as a convention by the community and it seemed very important to share it, the queries had the word query and the name as optional, with mutations it is not considered good practice using mutations without explicitly saying that it is, that is, we must always indicate the word mutation to use them.
Example of content creation mutation, we cannot test this in StarWars GraphQL so we will simulate how it should be sent to create a new character with its name and height:
# PersonInput debería de ser un tipo definido para recibir como parametro
mutation createPerson($person: PersonInput) {
createPerson(person: $person) {
name
height
}
}
The variables of this mutation should be sent like this:
{
"person": {
"name": "Marco",
"height": "50"
}
}
With this we conclude the basic principles of GraphQL queries, and now if we go to the code.
Let's go to Code 🧑💻
Repo https://github.com/mrcMesen/GraphQL-with-ReactJs-and-ApolloClient
Init the project with CRA:
yarn create react-app graphql-react-apolloclient --template typescript
Add ReactRouter:
yarn add react-router-dom
Create a basic structure and some styles, so far the git head is ef228bf
src
components
styles
App.tsx
index.tsx
// .src/App.tsx
// routing config
function App() {
return (
<Router>
<Layout>
<Switch>
<Route exact path='/'>
<SummaryHome />
</Route>
<Route path='/person-details/:id'>
<PersonDetail />
</Route>
</Switch>
</Layout>
</Router>
);
}
Initial View:

With this we have what we need from here we are going to start configuring GraphQL with Apollo Client.
Apollo Client
Add the library and additionally add graphql
yarn add @apollo/client graphql
I always try to separate the configuration towards the API, so I create a new folder named api
where I will have all the GraphQL configuration
We must create the Apollo Client,
this will have the initial configuration.
// src/api/apolloClient.ts
import { ApolloClient, InMemoryCache } from '@apollo/client';
export const apolloClient = new ApolloClient({
// API URL
uri: 'https://swapi-graphql.netlify.app/.netlify/functions/index',
cache: new InMemoryCache(),
});
Apollo client works with hooks, so in order to access these we must wrap the components that we want to access the hooks in the Apollo Provider
.
// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
// Importamos el Provider y el Client que acabamos de crear
import { ApolloProvider } from '@apollo/client';
import { apolloClient } from './api/apolloClient';
import './styles/index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<ApolloProvider client={apolloClient}>
<App />
</ApolloProvider>
</React.StrictMode>,
document.getElementById('root')
);
We are close to seeing the results 😬
We add a file in which we will have the queries
, in this we will use Apollo gql
this will allow us to define the queries
we will start with some very basic ones, only to be able to do the tests of the entire configuration.
// src/api/queries.ts
import { gql } from '@apollo/client';
export const ALL_PEOPLE = gql`
query GetAllPeople {
allPeople {
people {
name
}
}
}
`;
export const PERSON = gql`
query GetPerson {
person {
name
}
}
`;
To take advantage of TS we add a couple of interfaces from the responses that we will receive from the API.
// src/api/responses.ts
export interface Person {
name: string;
}
export interface AllPeople {
allPeople: {
people: Person[];
};
}
Now we are ready 😎. We will use the useQuery
hook provided by ApolloClient, this will facilitate all communication with the API and thanks to the Provider we should not be constantly manipulating the client
, so let's get to work:
// src/components/SummaryHome.tsx
import { ReactElement } from 'react';
import { useQuery } from '@apollo/client';
import { ALL_PEOPLE } from '../api/queries';
import { AllPeople } from '../api/responses';
export const SummaryHome = (): ReactElement => {
const { loading, error, data } = useQuery<AllPeople>(ALL_PEOPLE);
console.log(data);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
return (
<>
{data?.allPeople.people.map(person => (
<p>{person.name}</p>
))}
</>
);
};
This will give us a list of all the characters 🎉 🎊. As we can see, the useQuery
hook gives us loading, error, data
in this way we can use any query
created.
Now what remains is to improve the UI a bit and create the detail view, here are some improvements made to the code.
Improving the queries:
// src/api/queries.ts
import { gql } from '@apollo/client';
export const ALL_PEOPLE = gql`
query GetAllPeople {
allPeople {
people {
id
name
species {
name
}
}
}
}
`;
export const PERSON = gql`
query GetPerson($id: ID!) {
person(id: $id) {
id
name
birthYear
eyeColor
height
species {
name
}
homeworld {
name
}
}
}
`;
Improving the interfaces:
// src/api/responses.ts
export interface Person {
id: string;
name?: string;
birthYear?: string;
eyeColor?: string;
height?: number;
species?: {
name: string;
};
homeworld?: {
name: string;
};
}
export interface AllPeople {
allPeople: {
people: Person[];
};
}
Adding the detail view:
// src/components/PersonDetail.tsx
import { ReactElement } from 'react';
import { useParams } from 'react-router-dom';
import { useQuery } from '@apollo/client';
import { PERSON } from '../api/queries';
import { Person } from '../api/responses';
export const PersonDetail = (): ReactElement => {
const { id } = useParams<{ id: string }>();
const { loading, error, data } = useQuery<{ person: Person }>(PERSON, {
variables: {
id,
},
});
if (loading) return <p className='result'>Loading...</p>;
if (error) return <p className='result'>Error: {error.message}</p>;
return (
<div className='person-details'>
<h1>{data?.person.name}</h1>
<span> Especie: {data?.person.species?.name || 'Not specificate'}</span>
<span>Año de Nacimiento {data?.person.birthYear}</span>
<span>Altura: {data?.person.height}</span>
<span>Color de ojos: {data?.person.eyeColor}</span>
<span>Nombre de planeta: {data?.person.homeworld?.name}</span>
</div>
);
};

Conclusion
GraphQL is really a technology that I want to continue learning about, it just completely changes the way I handle web communication.
This is the first of many other articles that I want to write, I hope you liked it, I am open to any feedback! Greetings!