Angular Icon Get 73% off the Angular Master bundle

See the bundle then add to cart and your discount is applied.

0 days
00 hours
00 mins
00 secs

Write JavaScript like a pro. Javascript Icon

Follow the ultimate JavaScript roadmap.

GraphQL Resolvers: An in-depth look

Welcome back to the GraphQL series, we’re here to teach you how to use resolvers in GraphQL today and get you coding! If you’re new to GraphQL, it’d be well worth checking out my previous article The Missing GraphQL Introduction before we get going!

Follow this GraphQL Series:

With that out the way, let’s jump in. Resolvers are responsible for getting the data for our GraphQL server. Here’s a typical resolver function before we add some of the good stuff:

const resolver = (parent, args, context, info) => {
  return {};
};

Let’s have a quick high-level look at the parameters. The first is our parent resolver (if it exists). This will make more sense in the examples below as we continue. Next up, args can be passed in with the request. Think of posting JSON to a REST endpoint, this is how we access user input. The global context is given to every resolver. This would represent the logged in user, or any other global state you want all resolvers to have. Lastly, the info object is a more advanced feature, giving you access to details about the current query (this typically isn’t needed for every resolver, though!).

For a more in-depth look at these fields for your reference, check out the official site - GraphQL Resolver Fields.

Setting up our GraphQL Project

Now that we know the basics, let’s get coding! Head over to the github repo for the course, and checkout the resolvers-start branch. Here’s the steps:

One thing to take note of, I added in a piece of code that should hot reload any code as we change it. This does not always work perfectly, meaning that sometimes you will need to kill the server and run npm start again after making a code change (if it happens).

At this point, your terminal should say Server ready at http://localhost:4000. Let’s open that up. If you type in the following, on the left side you should see a response from our server on the right, with a couple books:

query {
  books{
    title
    author
  }
}

This example idea was taken from the official Apollo docs, and we will roll with it as a starting point, getting more in-depth as we go.

For now, the only files we will work with is the resolvers.js and typeDefs.js inside the src directory. This is where all of the magic happens to make our server work. Also, be warned; we will be going down some fun rabbit holes in this series, such as SQL query joins, dataloading, and authentication. I think you’ll find Apollo Server to be pretty simple in itself.

Type Defs

The GraphQL type system is straight forward, there are integers, floats, strings, arrays, and even support for custom types. You can head over and read more about them on the official website after this post - but let’s continue coding.

Now, open up your typeDefs.js in our source repo. It looks like this, you’ll notice type definitions inside:

import { gql } from 'apollo-server';

export const typeDefs = gql`
  type Book {
    title: String
    author: String
  }

  type Query {
    books: [Book]
  }
`;

In the current setup above, we can only get access to author names by querying for books.

Let’s take a hypothetical requirement that our frontend team needs to build an authors page for our book store, but they can’t do that right now with our current books query. They want to display all of the author names names and twitter handles. So let’s make it happen!

Adding a new type

Let’s create an Author type, by adding this to the top of (inside) our typeDefs template string literal:

type Author {
  name: String
  twitter: String
}

And then adding a query to the root Query type:

type Query {
  books: [Book]
  author: Author
}

The resulting file would be:

import { gql } from 'apollo-server';

export const typeDefs = gql`
  type Author {
    name: String
    twitter: String
  }

  type Book {
    title: String
    author: String
  }

  type Query {
    books: [Book]
    author: Author
  }
`;

It may feel like this is a tedious process by having to add a type for everything your API returns, but there are two big reasons this is useful. First off, the built-in documentation. As you will find out below, looking at any GraphQL docs will immediately give you insight into what queries you can make. Also, this provides the ability to intelligently cache data inside your client side JS applications. We will get into that in a later post, when we go over client side integration.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

Building a Query

Okay, now if we re-run npm start on the command line, our GraphQL playground shows we have an author query in the schema sidebar. Let’s try to run that query. To build a query, we always start with:

query {

}

The next piece is the name from inside of the Query type in the file above. In this case, it would be author:

query {
  author
}

Next, we look at what author returns. In this case, it is Author. In GraphQL, when a resolver returns an array or a single object, the syntax is the exact same and we just reference what looks like a property:

query {
  author {
    name
    twitter
  }
}

We will change this to return multiple offers soon, and you’ll see what I mean. Once you get used to this flow I described, it’s easy to quickly query ANY GraphQL endpoint, as long as you can access the schema documentation.

What happens if we try to run the query above? Open up localhost:4000 and try it. Hmm, the response back is:

{
  "data": {
    "authors": null
  }
}

Let’s “resolve” the situation!

Adding a resolver

We’re seeing null as we haven’t defined any resolvers yet. Let’s open resolvers.js and fix that up. Here’s the file in its current state:

export const resolvers = {
  Query: {
    books: () => {
      return [
        {
          title: 'Harry Potter and the Chamber of Secrets',
          author: 'J.K. Rowling',
        },
        {
          title: 'Jurassic Park',
          author: 'Michael Crichton',
        },
      ];
    },
  },
};

Since we added another field to the Query type, this resolver will live next to the books function. We can add it below like so:

export const resolvers = {
  Query: {
    books: () => {
      return [
        {
          title: 'Harry Potter and the Chamber of Secrets',
          author: 'J.K. Rowling',
        },
        {
          title: 'Jurassic Park',
          author: 'Michael Crichton',
        },
      ];
    },
    author: () => {
      return { name: 'Todd', twitter: 'toddmotto' };
    },
  },
};

Okay, now let’s run our query from earlier:

query {
  author {
    name
    twitter
  }
}

And here we go, you’ll now see:

{
  "data": {
    "author": {
        "name": "Todd",
        "twitter": "toddmotto"
      }
  }
}

That wasn’t too hard was it? I hope as a developer you start to understand that GraphQL itself, isn’t a huge thing to learn. However, all of the tooling around it and features you may want, sure are.

Once we realize this resolvers.js object is too large, we have to create a nice way to split that out.

What happens when our typeDefs gets to be 300 lines long? How do we authenticate users for some of our resolvers? How do we efficiently load data?

As you can see, there are many topics we still have to cover.

Arrays vs single Object

Next up, I want to change one thing with our typeDefs and resolver to reiterate the point I made earlier. Arrays and Objects have the same query signature. If we go into typeDefs.js and change Author to [Author] and make the name plural, what happens? Your Query type inside typeDefs should now be:

type Query {
  books: [Book]
  authors: [Author]
}

After that, we need to change author to authors and make an array inside resolvers.js:

// From this:
author: () => {
  return { name: 'Todd', twitter: 'toddmotto' };
};

// to this
authors: () => {
  return [
    { name: 'Todd', twitter: 'toddmotto' },
    { name: 'React', twitter: 'reactjs' },
  ];
};

Now our response looks correct when making the query:

{
  "data": {
    "authors": [
      {
        "name": "Todd",
        "twitter": "toddmotto"
      },
      {
        "name": "React",
        "twitter": "reactjs"
      }
    ]
  }
}

Remember, our resolver returned an object with a single author before, but now it returns an array. Our query syntax never changed:

query {
  authors {
    name
    twitter
  }
}

This is one thing to stay aware of when using GraphQL. When only looking at the query itself, you can’t know exactly how the data is going to be returned, but we can have a good guess. At any time, you can pull up the playground and find out for sure.

Querying with REST

Let’s jump into another topic. Most people reading this will likely be using existing REST APIs in their apps. What would happen if you decided to start building out a GraphQL server, and then realized an existing REST endpoint, or service needed to pull in something from your new GraphQL service? Thankfully this is not difficult to do. Since Apollo runs using GET or POST requests, we can get data using a normal REST call.

First, we take the query we want to run, turn it into a single line, and add commas between the variables we want. The one above would become:

authors{name,twitter}

Using any HTTP client, we can get the data. Here’s an example using curl:

curl -g "http://localhost:4000?query={authors{name,twitter}}"

To sum this up, any existing microservice, or code that doesn’t have access to Apollo client (we’ll go over this in our next post), can make a normal GET (or POST) to our server. You can read more in-depth about this on the official GraphQL docs as well.

Mutations

Now that you have a solid grasp on making a query, let’s focus on mutations. In my opinion, a mutation is a special query, that lets everyone know we are modifying data on the server. Whether that would be a POST PATCH PUT or DELETE in a REST API, is anyone’s guess. With GraphQL, it is not so granular. As I mentioned in the first post, we do not have to specify request methods, or worry about status codes.

Our first GraphQL mutation

Let’s start with typeDefs.js once again. This time, let’s make a new type, called Mutation, and add it to the bottom:

type Mutation {
  addAuthor: Author
}

If you restart the server (npm start), you should see a new section for Mutations in the schema docs. If we open up resolvers.js, you may be able to guess what we need to add to the bottom:

Mutation: {
  addAuthor: () => {
    return {
      name: 'Zach',
      twitter: 'zachcodes',
    };
  },
}

Calling a mutation in the playground is the same process as running a query, except this time we use the mutation keyword. Run this inside the playground:

mutation {
  addAuthor {
    name
    twitter
  }
}

As we would expect, we get back:

{
  "data": {
    "addAuthor": {
      "name": "Zach",
      "twitter": "zachcodes"
    }
  }
}

Input Types

What we’ve achieved above is great, but how do we send in the name and twitter to the mutation? Welcome to input types.

Open up typeDefs and add this to the top:

input AddAuthorInput {
  name: String
  twitter: String
}

Next we need to tell Apollo that we want to use this type for our mutation. We do this by changing the Mutation type to:

type Mutation {
  addAuthor(input: AddAuthorInput!): Author
}

Notice the exclamation !. This means that a type is required. In this case, we must pass the input type with the mutation or else it will fail. Since we now understand that, we should update a few other types as well. Here’s what my typeDefs looks like:

import { gql } from 'apollo-server';

export const typeDefs = gql`
  input AddAuthorInput {
    name: String!
    twitter: String
  }

  type Author {
    name: String!
    twitter: String
  }

  type Book {
    title: String!
    author: String!
  }

  type Query {
    books: [Book]
    authors: [Author]
  }

  type Mutation {
    addAuthor(input: AddAuthorInput!): Author
  }
`;

My thought here is that all books must return a title and author, and all authors must have a name, but a twitter handle can be optional. Restart the GraphQL server, and try making the same mutation as before, without any changes.

You should see an error like this:

Field \"addAuthor\" argument \"input\" of type \"AddAuthorInput!\" is required, but it was not provided.

As you can see, GraphQL’s type system gives us some handy features for requiring fields. Now let’s try updating our mutation to work correctly. It looks like this:

mutation {
  addAuthor(input:{name: "Test", twitter: "Test"}) {
    name
    twitter
  }
}

Input types represent JSON objects. This makes it easy to pass multiple arguments into a query, or mutation. If you ran the mutation again, you may be wondering: How do we use the input type we just created? For the first time in this post, we can start using the variables that Apollo passes in to our resolver functions! Let’s update our addAuthor resolver to use them:

addAuthor: (_, { input: { name, twitter } }) => {
  return {
    name,
    twitter,
  };
},

Remember, the first argument is a parent resolver, which we do not currently have. In this case, the convention is to use an underscore _ since it isn’t used.

Running the mutation will now give us back whatever we passed in:

{
  "data": {
    "addAuthor": {
      "name": "Test",
      "twitter": "Test"
    }
  }
}

If you followed along closely, you may start to see how you can use GraphQL to accomplish anything you could with a REST API, but with some type safety, better documentation, and ease of use.

You can git checkout resolvers-end to see the final code that we created in the post if you would like to use it as a reference.

In the next post, we will cover how asynchronous data can be resolved, batch loading data from a database as efficiently as possible, and authentication.

If you come from a frontend background, don’t worry, we will be getting into client side interactions soon enough :)

Thanks for reading, and see you in the next post!

Learn JavaScript the right way.

The most complete guide to learning JavaScript ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover