7. Resolvers
15m

Overview

It's time to introduce the functions that make it possible to fetch data for in our schema.

In this lesson, we will:

  • Explore what are, and what parameters they accept
  • Learn how to access from a function

Introducing resolvers

A resolver's mission is to populate the data for a in your schema.

What exactly is a ? A resolver is a function. It has the same name as the that it populates data for. It can fetch data from any , then transforms that data into the shape your client requires.

Hand-drawn illustration depicting a resolver function retrieving data from data-land

In the src directory, we'll start by creating a new resolvers.ts file.

In that file, we'll declare a resolvers constant, assigning it an empty object for now. Let's export it, because we'll need it in our server config options.

src/resolvers.ts
export const resolvers = {};

Our resolvers object's keys will correspond to our schema's types and .

To create a for the featuredPlaylists , we'll first add a Query key to our resolvers object. The value of that key will be another object that contains the featuredPlaylists key.

resolvers.ts
export const resolvers = {
Query: {
featuredPlaylists: () => {},
},
};

Notice how our object follows the structure of our schema? featuredPlaylists is a on the Query type, so we define it as a property on the Query key.

The shape of a resolver

How will our interact with our ? This is where a resolver's parameters come in. Resolver functions have a specific signature with four optional parameters: parent, args, contextValue, and info.

Hand-drawn illustration depicting a resolver function signature with its four parameters
featuredPlaylists: (parent, args, contextValue, info) => {},

Let's go over each parameter briefly to understand what they're responsible for:

  • parent:
    parent is the returned value of the for this 's parent. This will be useful when dealing with resolver chains.
  • args:
    args is an object that contains all that were provided for the by the GraphQL . When for a specific item (such as a specific track instead of all tracks), in client-land we'll make a with an id that will be accessible via this args parameter in server-land.
  • contextValue:
    contextValue is an object shared across all that are executing for a particular . The resolver needs this to share state, like authentication information, a database connection, or in our case the RESTDataSource.
  • info:
    info contains information about the 's execution state, including the name, the path to the field from the root, and more. It's not used as frequently as the others, but it can be useful for more advanced actions like setting cache policies at the level.

We'll explore the first three parameters in this course, starting with contextValue, the third positional parameter.

Accessing data sources in resolvers

As mentioned above, contextValue is an object that is shared across all . It's how resolvers get access to the same shared state, such as .

Let's see this in action by adding parameters to our featuredPlaylists .

resolvers.ts
featuredPlaylists: (parent, args, contextValue, info) => {},

The order of the parameters matters here. If we only added one parameter, then the function would consider it as the first optional parameter: parent. We need the contextValue, which is the third parameter, to access our .

We don't need the first two parameters, so as a convention, we'll name them with underscores: one underscore for the first (parent) and two underscores for the second (args). For the contextValue, we'll destructure it to access its child object dataSources. And we can omit the fourth parameter, info, as we won't use it.

resolvers.ts
featuredPlaylists: (_, __, { dataSources }) => {},

The dataSources property is where an instance of our SpotifyAPI class will live.

Accessing spotifyAPI

We'll hook up the SpotifyAPI class to our in the next lesson. For now, we'll assume that dataSources will contain a property called spotifyAPI (written in lowercase by convention, as it's an instance of the SpotifyAPI class.) We can call this instance's getFeaturedPlaylists method right here in our —and the method does the rest of the work for us!

resolvers.ts
Query: {
featuredPlaylists: (_, __, { dataSources }) => {
return dataSources.spotifyAPI.getFeaturedPlaylists();
},
}

Tip: As a best practice, when working on your and , try to keep resolver functions as focused as possible. By doing so, you make your API more resilient to future changes. You can safely refactor your data fetching code, or change the source entirely from a REST API to a database, without breaking your API. This also keeps your resolvers readable and easier to understand, which comes in handy as you define more and more of them!

Right about now, you're likely seeing some red squiggly errors in your IDE. TypeScript is complaining that we've given a number of parameters to our featuredPlaylists , but we haven't specified what type of data they are. Instead, we see that almost everything is inferred as an any type.

We can actually use the types and in our schema to help clarify these data types—but we don't have to write the specific type annotations by hand! We'll see just how to tackle this in the next lesson.

Practice

Which of the following are true about resolvers?
What is the contextValue parameter useful for?

Key takeaways

  • are functions that can be defined for each in our schema. They accept four optional parameters, parent, args, contextValue, and info, and they're responsible for returning the data for a particular when it's queried.

Up next

Our and functions are still missing something: type annotations! This means we're seeing some TypeScript errors in our code. We could write these types by hand, but there's a more efficient way. In the next lesson, we'll explore how to generate TypeScript types from our .

Previous

Share your questions and comments about this lesson

This course is currently in

beta
. Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.