Code with Hugo: A gentle introduction to GraphQL
A gentle introduction to GraphQL API integrations
GraphQL is a great alternative to REST (or other HTTP API designs). This is an quick introduction to the core concepts around consuming a GraphQL API.
To see some examples consuming a GraphQL API:
- In Python, see Python GraphQL client requests example using gql
- In JavaScript (browser and Node), see last week’s Code with Hugo newsletter
What is GraphQL and what problems does it solve?
GraphQL is “a query language for your API”.
In plain English, it makes the client define what (nested) data it needs.
If we compare it to REST approaches:
- the “pure” REST approach is to return IDs (or resource links) for any associations (or nested resources).
- The less pure approach is to expand all the nested stuff.
The first situation leads to having to make lots of calls to fetch all the data, the second leads to huge payloads and slow load times.
In GraphQL, the client states in the request what it wants expanded, renamed or whatever else in the response.
It has some nice side-effects, eg. less need to version your API since the client defines what it wants and GraphQL has a way to deprecate fields.
Schema
GraphiQL, “An in-browser IDE for exploring GraphQL.” is available by navigating to the endpoint in your browser. It’s possible to generate the schema using the GraphQL CLI (requires Node + npm 5+):
npx graphql-cli get-schema --endpoint $BASE_URL/api/graphql --no-all -o schema.graphql
Queries
GraphQL query concepts
Fields
What we would like returned in the query, see the GraphQL documentation for “fields”.
The GraphQL query for that returns the fields name
, fleeRate
, maxCP
, maxHP
, is the following (see output here):
{ pokemon(name: "Pikachu") { name fleeRate maxCP maxHP } }
Arguments
How we are going to filter the query data down, see the GraphQL documentation for “arguments”.
To get the names of the first 10 pokemon we use pokemons (first: 10) { FIELDS }
see the output here:
{ pokemons (first: 10) { name fleeRate maxCP maxHP } }
Aliases
Aliases give us the ability to rename fields, see the GraphQL documentation for “aliases”. We’re actually going to use it to map fields in the query eg. from camel to snake case:
{ pokemon(name: "Pikachu") { evolution_requirements: evolutionRequirements { amount name } } }
Running this query (here) gives us the following, where the evolutionRequirements
is what we’ve aliased it to.
{ "data": { "pokemon": { "evolution_requirements": { "amount": 50, "name": "Pikachu candies" } } } }
Fragments
The definition of fields to be expanded on a type. It’s a way to keep the queries DRY and in general split out field definitions that are repeated, re-used or deeply nested, see the GraphQL documentation for fragments. It’s going to mean that instead of doing (see the query in action here):
{ pokemon(name: "Pikachu") { weight { minimum maximum } height { minimum maximum } } }
We can for example run this (query here):
{ pokemon(name: "Pikachu") { weight {...FullPokemonDimensions} height {...FullPokemonDimensions} } } fragment FullPokemonDimensions on PokemonDimension { minimum maximum }
The output is equivalent:
{ "data": { "pokemon": { "weight": { "minimum": "5.25kg", "maximum": "6.75kg" }, "height": { "minimum": "0.35m", "maximum": "0.45m" } } } }
Running a GraphQL query
A GraphQL query can be run over POST or GET, it consists of:
POST (recommended)
- Required headers:
Content-Type: application/json
- Required JSON body parameter:
query: { # insert your query }
Raw HTTP request
POST / HTTP/1.1 Host: graphql-pokemon.now.sh Content-Type: application/json { "query": "{ pokemons(first: 10) { name } }" }
cURL
curl -X POST \ https://graphql-pokemon.now.sh/ \ -H 'Content-Type: application/json' \ -d '{ "query": "{ pokemons(first: 10) { name } }" }'
GET
- Required query param:
query
raw HTTP request
GET /?query={%20pokemons(first:%2010)%20{%20name%20}%20} HTTP/1.1 Host: graphql-pokemon.now.sh
cURL
curl -X GET 'https://graphql-pokemon.now.sh/?query={%20pokemons%28first:%2010%29%20{%20name%20}%20}'
Top-level queries
There are 2 types of queries on the GraphQL Pokemon API at the moment:
- First X pokemon: get all items (with whatever fields are defined in the query)
- Single Pokemon by name: get a single item by its slug (with whatever fields are defined in the query)
- Single Pokemon by id: get a single item by its slug (with whatever fields are defined in the query)
First X Pokemon
Queries of the form (see it in action in GraphiQL):
{ pokemons(first: 5) { name # other fields } }
Single Pokemon by name
Queries of the form (see it in action in GraphiQL):
{ pokemon(name: "Pikachu") { name classification # other fields } }
Note the double quotes (
""
) around the argument value
Single Pokemon by id
Queries of the form (see it in action in GraphiQL):
{ pokemon(id: "UG9rZW1vbjowMjU=") { name classification # other fields } }
Note the double quotes (
""
) around the argument value
Sample queries
Get some Pokemon to create strengths/weakness/resistance classification
Query (see it in GraphiQL):
{ pokemons(first: 100) { name image maxHP types weaknesses resistant } }
Get Pokemon and evolutions expanded for physical stats and attacks
Query (see it in GraphiQL):
{ pokemon(name: "Pikachu") { ...PokemonWithAttack ...FullPhysicalStats evolutions { ...FullPhysicalStats ...PokemonWithAttack } } } fragment PokemonWithAttack on Pokemon { name attacks { fast { name type damage } special { name type damage } } } fragment FullPhysicalStats on Pokemon { height { ...FullDimension } weight { ...FullDimension } } fragment FullDimension on PokemonDimension { minimum maximum }
Get selected Pokemon as named fields with their evolution names
Query (see it in GraphiQL).
We can rename top-level queries using aliases, that’s helpful if we want to do the following:
{ pikachu: pokemon(name: "Pikachu") { ...FullPokemon evolutions { ...FullPokemon } } bulbasaur:pokemon(name: "Bulbasaur") { ...FullPokemon evolutions { ...FullPokemon } } } fragment FullPokemon on Pokemon { name }
There will be more GraphQL content coming out this week on the blog (from last week’s newsletter), follow me on Twitter @hugo__df to stay informed.