8. 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.

πŸ“‚ src
┣ πŸ“‚ datasources
┣ πŸ“„ graphql.d.ts
┣ πŸ“„ helpers.ts
┣ πŸ“„ index.ts
┣ πŸ“„ resolvers.ts
β”— πŸ“„ schema.graphql

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 featuredListings , we'll first add a Query key to our resolvers object. The value of that key will be another object that contains the featuredListings key.

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

Notice how our object follows the structure of our schema? featuredListings 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
featuredListings: (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 listing instead of all listings), 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 featuredListings .

resolvers.ts
featuredListings: (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
featuredListings: (_, __, { dataSources }) => {},

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

Accessing ListingAPI

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

resolvers.ts
Query: {
featuredListings: (_, __, { dataSources }) => {
return dataSources.listingAPI.getFeaturedListings();
},
}

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 featuredListings , but we haven't specified what type of data they are. Instead, we see that almost everything is inferred as an any type.

TypeScript errors in our IDE
Parameter '_' implicitly has an 'any' type.
Parameter '__' implicitly has an 'any' type.
Binding element 'dataSources' implicitly has an 'any' type.

Let's find out how to fix these type errors in the next lesson.

Practice

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

Write a resolver function for the Query.popularPlanets field. Define the resolver inside the resolvers object. Use the resolver's contextValue argument to access the server's dataSources property, and return the results of calling planetService.getPopularPlanets().

Loading...
Loading progress

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

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.