Schemas and Resolvers in GraphQL

Joshua Hall

In this article we’ll be looking into creating our own data structures in a GraphQL API. Everything we’ll be going over can be explored deeper in the official GraphQL docs.

Prerequisites

Since we are setting up a basic API, it would be best to first know how to make queries to test our data.

Installation

For the sake of simplicity I recommend using Prepros, or babel, so we can use some of the latest JavaScript features like import.

If you use Prepros, make sure the Babel and Auto Compile options are on for your server.js file

For setting up our server, we’ll be using graphql-yoga, and nodemon. nodemon will allow us to have automatic serve reload on file changes and graphql-yoga gives us simple tooling to setup an Express-based GraphQL server.

$ npm i graphql-yoga nodemon

nodemon Setup

Lets’ start with getting nodemon working. Under scripts in the project’s package.json file, we’ll just add "start": "nodemon server.js", so instead of running node server we’ll run npm run start.

package.json

{
  "name": "graphql-api",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "dependencies": {
    "graphql-yoga": "^1.16.7"
  },
  "devDependencies": {
    "nodemon": "^1.19.1"
  },
  "scripts": {
    "start": "nodemon server.js"
  },
  "author": "",
  "license": "ISC"
}

Server Setup

Over in our main file we’re going to import GraphQLServer from graphql-yoga to get our API working on localhost:4000. Our server is going to need two things, our type definitions, which will initialize and structure our data, and our resolvers, which tell the server how to get and filter the data. To keep things organized we’re going to create variables for our definitions and resolvers and pass those to our server function.

server.js

import { GraphQLServer } from 'graphql-yoga'

const typeDefs = ``; // Our definitions need to be in template literals

const resolvers = {};

const server = new GraphQLServer({ typeDefs, resolvers }) ;

server.start(() => console.log('server running'));

This will return an error for now since we haven’t set up a schema in typeDefs yet.

Type Definitions

We can set up a basic query by simply adding type Query {} with a list of the data we want to make available with what type the return data will be. While we can make our own, GraphQL comes with a few baked in types; String, Int (all real numbers), Float (decimal numbers), Boolean, ID, and any of these placed in brackets is an array of that type. Any type ending with a ! is set as a non-nullable type, meaning it will return an error if nothing is sent back.

Here’s an example of different possible variations. Note that you wouldn’t have multiple variations for the same field, as this is just for illustrative purposes:

server.js

const typeDefs = `
  type Query {
    user: String      # null
    user: String!     # error 'Cannot return null for non-nullable field Query.user'
    users: [String!]! # must return an array of non-null strings
  }
`;

Over in localhost:4000 you’ll notice that you no longer get an error when typing any of these, but will get an error or null when you run the query since we haven’t told it how to get the data yet.

Resolvers

To be able to get data, we need to make a function with the same name as what we’re querying, and have it return what we want.

server.js

const resolvers = {
  Query: {
    user(){
      return 'I am a string';
    },
    users(){
      const names = ['Chomp', 'Jaws', 'Alli'];
      return names;
    }
  }
};

Custom Types

Types like String, Int, and Boolean are just what comes with GraphQL out-of-the-box, but we can also make our own types that will return much more intricate data and pass that type to our query to make it requestable.

Outside of Query but still in typeDefs we can make our own type like User (it’s best practice for custom types to be capitalized) and set up the data we want it to have. Just like in Query, each item in our type must also be typed, like age: Int!, and we’ll have the data returned by the corresponding resolver.

server.js

const typeDefs = `
  type Query {
    user: User! # Must be of our custom type, User
  }

  type User {
    name: String!
    age: Int!
    friends: [String]!
  }
`;

And over in our resolver we can just pass back an object with our data.

const resolvers = {
  Query: {
    user(){
      return {
        name: 'Chomp',
        age: 47,
        friends: ['Alli', 'Jaws']
      };
    }
  }
};

Data Setup

In a real-world scenario we wouldn’t be returning all of our data in the resolvers like this, but actually getting it from some other source like a database. Instead of worrying about a backend we’re just going to set up some dummy data to use.

const users = [
  {
    name: 'Chomp',
    age: 47,
    friends: ['Alli', 'Jaws']
  },{
    name: 'Alli',
    age: 16,
    friends: ['Chomp', 'Jaws']
  },{
    name: 'Jaws',
    age: 35,
    friends: ['Alli', 'Chomp']
  },
];

Since we’re just going to get the whole array of users, we’ll tell typeDefs we want an array of type User and the resolver will just return the whole users array.

const typeDefs = `
 type Query {
    user: [User!]! 
  }

  type User {
    name: String!
    age: Int!
    friends: [String]!
  }
`;

const resolvers = {
  Query: {
    user(){
      return users;
    }
  }
};

Arguments

Right now our only options are to return one specific piece of data, everything. What if we want to filter the response down to what we want?

We can do that by setting up arguments in our query and using them in conditional statements in the resolver. Our arguments must also be typed.

const typeDefs = `
 type Query {
    user(name: String!): [User!]! 
  }
`;

In our resolvers we have access to some very useful objects that GraphQl gives us. For now all we need are parent and args.

  • parent: Info on the parent element when you have nested custom types. So if we had a User type which accessed a Post type, each post would have access to the data on the user.
  • args: All of our arguments passed into the query.
  • ctx: Short for context, anything that the query made may need like authentication data.
  • info: Information about the state of the query.

Here, we’re going to check if there’s any argument at all on args and, if there is, use JavaScript’s filter() to return only the users whose name matches our argument.

server.js

const resolvers = {
  Query: {
    user(parent, args, ctx, info){
      if (!args.name) return users;

      else return users.filter(user => {
        return user.name.toLowerCase().includes(args.name.toLowerCase());
      });
    }
  }
};

Over in localhost:4000 we can make use of our argument like this:

{
  user(name: "alli") {
    name
    friends
  }
}

Relational Data

To allow more nested data we can add our custom types into other custom types. When we do that, we need to add another resolver outside of Query for that type of data.

We’re going to make it so whenever we get a user we can get back the user data for each of their friends.

server.js

Our resolver is going to create an empty object, loop over each friend on parent to put every matching user into the friends array, and return friends to our query.

const typeDefs = `
 type Query {
    user(name: String!): [User!]!
  }

  type User {
    name: String!
    age: Int!
    friends: [User!]!
  }
`;

const resolvers {
  Query: { ... },
  User: { // The nested type that we're querying for
    friends(parent, args, ctx, info) { 
        const friends = [];

        parent.friends.forEach(friend => users.filter(user => {
          if (user.name.toLowerCase().includes(friend.toLowerCase())) friends.push(user);
        }));

        return friends;
    }
  }
};

Conclusion

While there’s still much more to explore with GraphQL, I hope this worked as a thorough enough introduction to setting up our own API.

If you had any difficulty getting our example to work properly you can always reference this repo for it.

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#