GraphQL Queries

Learn how a query works, and add a new one
This post is part of a Series of posts, and may not make a lot of sense without reading the previous installments.

Introduction

Let’s look at how the pokemon query works for our GraphQL Backend we worked on a couple of posts ago.

Query Anatomy

The following sections will go over the different parts in the project structure where the pokemon query is defined and implemented.

Query Definition

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.

Types involved

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

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.

Query Implementation

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

paramdescription
objThis is a reference to a parent object, and is useful at the point when resolvers start calling other resolvers.
argsAn object containing argument values from the query call.
ctxSome context shared by all resolvers in the query.
infoInformation 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.

Section outro

This concludes dissecting the pokemon query. Let’s move on to creating a new query to retrieve information about a move.

A New Query

Let’s create a new query to return information about a pokemon move.

Definition

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.

Types

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
}

Implementation

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 (?)

Some Move Data

We need some data for pokemon moves. Check out file /data/moves.json.

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
}

Grab data logic

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;

Deploy our new changes

To deploy to our backend endpoint, let’s do

> npm run deploy-gql

Test

Branch query-moves

Changes made on this post, were commited to the repo on a new branch named query-moves

Section outro

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

  • /data/moves.json
  • /g/resolvers/Query/move.js

and we updated file

  • /g/entities/_Query.gql

to add the new query definition amongst the other existing queries.

Conclusion

Hopefully you now have a better understanding of the backend and how to add more and more queries.

What next

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



Back to Post List
ok!