Odyssey

Intro to GraphQL with TypeScript & Apollo Server

Course overview and setupGraphQL basicsSchema definition language (SDL)Building the schemaApollo ServerApollo Sandbox ExplorerThe listings REST APIResolversCodegenQuerying real dataQuery argumentsAdding the Amenity typeResolver chainsMutations
8. Resolvers
3m

Overview

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

In this lesson, we will:

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

Introducing resolvers

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

What exactly is a resolver? A resolver is a function. It has the same name as the field that it populates data for. It can fetch data from any data source, 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 fields.

To create a resolver for the featuredListings field, 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 resolver object follows the structure of our schema? featuredListings is a field on the Query type, so we define it as a property on the Query key.

The shape of a resolver

How will our resolvers interact with our data source? 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 resolver for this field's parent. This will be useful when dealing with resolver chains.
  • args:
    args is an object that contains all GraphQL arguments that were provided for the field by the GraphQL operation. When querying for a specific item (such as a specific listing instead of all listings), in client-land we'll make a query with an id argument that will be accessible via this args parameter in server-land.
  • contextValue:
    contextValue is an object shared across all resolvers that are executing for a particular operation. The resolver needs this argument to share state, like authentication information, a database connection, or in our case the RESTDataSource.
  • info:
    info contains information about the operation's execution state, including the field 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 resolver 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 resolvers. It's how resolvers get access to the same shared state, such as data sources.

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

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 data sources.

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 GraphQL server 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 resolver—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 resolvers and data sources, 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 resolver, 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().

Key takeaways

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

Up next

Our data source and resolver 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 GraphQL schema.

Previous
Next

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.

              fields

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              resolvers

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              fields

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              resolvers

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              arguments

              A key-value pair associated with a particular schema field that lets operations pass data to that field's resolver.

              Argument values can be hardcoded as literal values (shown below for clarity) or provided via GraphQL variables (recommended).

              query GetHuman {
              human(id: "200") {
              name
              height(unit: "meters")
              }
              }
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              querying

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              argument

              A key-value pair associated with a particular schema field that lets operations pass data to that field's resolver.

              Argument values can be hardcoded as literal values (shown below for clarity) or provided via GraphQL variables (recommended).

              query GetHuman {
              human(id: "200") {
              name
              height(unit: "meters")
              }
              }
              resolvers

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              argument

              A key-value pair associated with a particular schema field that lets operations pass data to that field's resolver.

              Argument values can be hardcoded as literal values (shown below for clarity) or provided via GraphQL variables (recommended).

              query GetHuman {
              human(id: "200") {
              name
              height(unit: "meters")
              }
              }
              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              resolvers

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              GraphQL server

              A server that contains a GraphQL schema and can resolve client-requested operations that are executed against that schema.

              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              resolvers

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              Resolvers

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              GraphQL schema

              A GraphQL schema defines the structure and types of data that can be queried or mutated, serving as a contract between the server and clients.

              NEW COURSE ALERT

              Introducing Apollo Connectors

              Connectors are the new and easy way to get started with GraphQL, using existing REST APIs.

              Say goodbye to GraphQL servers and resolvers—now, everything happens in the schema!

              Take the course