Let’s look at how the pokemon
query works for our GraphQL Backend we worked on a couple of posts ago.
The following sections will go over the different parts in the project structure where the pokemon
query is defined and implemented.
We define our queries
in file /g/entities/_Query.gql
. This file defines the Query
type, which includes the queries exposed on the endpoint.
# pokemon query
pokemon(id: String!): Pokemon
This query accepts a parameter named id
, which is of type String
and is required !
; the exclamation mark ( !
) means the parameter is required.
The pokemon
query returns a type of Pokemon
.
I have defined the Pokemon
type, and other related types in file /g/entities/Pokemon.gql
.
type Pokemon {
id: String!
name: String!
names: String!
type1: String!
type2: String
family: String!
kmBuddyDistance: Int!
stats: Stats!
moves: MoveSet!
height: Float!
weight: Float!
evolutionBranch: [Evolution]!
isTransferable: Boolean!
isDeployable: Boolean!
isLegend: Boolean!
isMythic: Boolean!
parentId: String
}
type Stats {
attack: Int!
defense: Int!
stamina: Int!
}
type MoveSet {
quick: [String]!
charge: [String]!
eliteQuick: [String]!
eliteCharge: [String]!
}
type Evolution {
evolution: String
form: String
candyCost: Int!
}
type MegaEvolution {
type: String!
stats: Stats!
type1: String!
type2: String!
}
type Move {
Name: String!
Type: String!
Power: Int!
DurationMs: Int!
Energy: Int!
combat: CombatMove
}
type CombatMove {
Type: String!
Power: Int!
Energy: Int!
buffs: MoveBuff
}
type MoveBuff {
buffActivationChance: Float!
targetDefenseStatStageChange: Int
targetAttackStatStageChange: Int
attackerAttackStatStageChange: Int
attackerDefenseStatStageChange: Int
}
type PokemonPage {
items: [Pokemon]!
paging: Paging!
}
types
define an object/entity, which are composed of scalar properties (i.e. String, Int, Float, Boolean) and other types
, like Stats
, MoveSet
, Evolution
, etc.
More info on defining types here.
Our backend now knows the query definition, and the return type(s).
It’s time to implement the logic.
Because the query name is pokemon
, the implementation will go into a file called pokemon.js
Because it is a query, this file will go into folder /g/resolvers/Query
// grab our data from the JSON file in the /data folder
let pokemon = require('./../../../data/pokemon.json');
// logic triggered when running the query
var resolver = (obj, args, ctx, info) => {
let id = args.id;
let pokem = null;
pokemon.forEach((item) => {
if (item.id === id.toUpperCase()) {
pokem = item;
}
});
return pokem;
};
module.exports = resolver;
A query (or mutation) implementation, we are calling a resolver
.
It takes in 4 parameters
param | description |
---|---|
obj | This is a reference to a parent object, and is useful at the point when resolvers start calling other resolvers. |
args | An object containing argument values from the query call. |
ctx | Some context shared by all resolvers in the query. |
info | Information about the query execution state. |
For our pokemon
query, the args
parameter looks like:
{
id: 'ivysaur'
}
Our pokemon resolver implementation, iterates the array of pokemon in the JSON
, finds a matching item.id
to the user parameter id
and returns the match.
This concludes dissecting the pokemon
query. Let’s move on to creating a new query
to retrieve information about a move
.
Let’s create a new query
to return information about a pokemon move
.
move(id: String!): Move
It follows a similar definition to the pokemon
query; It accepts a required argument id
, and returns a type of Move
.
Notice the type Move
already exists in the file /g/entities/Pokemon.gql
.
Here it is for completeness:
and itself depends on some other types
type Move {
Name: String!
Type: String!
Power: Int!
DurationMs: Int!
Energy: Int!
combat: CombatMove
}
type CombatMove {
Type: String!
Power: Int!
Energy: Int!
buffs: MoveBuff
}
type MoveBuff {
buffActivationChance: Float!
targetDefenseStatStageChange: Int
targetAttackStatStageChange: Int
attackerAttackStatStageChange: Int
attackerDefenseStatStageChange: Int
}
Time for our move
resolver.
// import things here
var resolver = (obj, args, ctx, info) => {
let id = args.id;
return ... // a Move object
};
module.exports = resolver;
Ok… so above is a bare-bones resolver
function…
What do we need now (?)
We need some data for pokemon moves. Check out file /data/moves.json
.
The moves.json
file is a bit different than the pokemon.json
file. moves.json
is a big map or the moves keyed by the move name.
{
"VINE_WHIP_FAST": {
"Name": "VINE_WHIP_FAST",
"Type": "GRASS",
"Power": 7,
"DurationMs": 600,
"Energy": 6,
"DamageWindowStartMs": 350,
"DamageWindowEndMs": 600,
"Accuracy": 1,
"StaminaLossScalar": 0.01,
"TrainerLevelMin": 1,
"TrainerLevelMax": 100,
"combat": {
"Type": "GRASS",
"Power": 5,
"Energy": 8
}
},
... all the moves as properties
}
So to find a move we are going to test if the move id
exists and return it.
// import things here
let moves = require('./../../../data/moves.json');
var resolver = (obj, args, ctx, info) => {
let id = args.id;
let move = null;
if (moves[id.toUpperCase()]) {
move = moves[id.toUpperCase()];
}
return move;
};
module.exports = resolver;
To deploy to our backend endpoint, let’s do
> npm run deploy-gql
Changes made on this post, were commited to the repo on a new branch named query-moves
In this section we created a new query for our graphql
endpoint.
If we look at the commit, we can see that we added 2 files
and we updated file
to add the new query definition amongst the other existing queries.
Hopefully you now have a better understanding of the backend and how to add more and more queries.
Whenever I need to make use of a server API on future posts, I will probably come back to this backend and keep adding to it.
Hold on!!! I made another post on this series about another query. allMoves query