You are viewing the preview version of this book
Click here for the full version.

Resolvers

So far, all we've talked about were abstract concepts, such as the schema defining GraphQL constructs and client queries with fields that the response should contain. But how the backend goes from receiving the query and producing the response? Where does the data comes from? This is what the resolvers are for.

Resolvers provide the response to client queries

For example, the schema might define a user query that returns a User object:

# schema
type User {
  username: String!
  email: String
  bio: String
}

type Query {
  user(username: String!): User
}

Then the client sends a request with a query for a user:

# query
query MyQuery {
  user(username: "user1") {
    username
    bio
  }
}

What database query to send to retrieve this user object?

Of course, this is highly dependent on the architecture. Users might be stored in an SQL database, so fetching the one with the given username needs an SQL statement. Or they might be in DynamoDB, a common choice in AWS, and that needs a signed HTTPS request. Or a user directory, or an external system.

Resolvers define the mechanism that connect to the data sources behind the GraphQL API, such as databases, and provide the values for the queries. In practice, most of the time spent working on a GraphQL backend is spent writing resolvers.

How to write resolvers, such as the language they are written in, is highly dependent on the specific GraphQL server you are using. For example, GraphQL.js uses Javascript, and AWS AppSync uses Velocity templates (VTL) as the primary way.

In this chapter, we'll focus on the general concepts of how resolvers work that are the same for all implementations. Then we'll take a detailed look into how AWS AppSync works in the second part of the book.

A resolver provide a value for a field in the response. Fields, as we've covered in the Fields chapter, are both the properties of types (the username of a User type) and the queries or mutations (the user query).

Note

Resolvers provide values for fields.

Top-level fields

(Official docs)

Let's first consider the top-most part of a client query! This is the field of the Query type and that is the entry-point to the object graph.

Let's see it through an example!

The schema defines a query that returns a String:

# schema
type Query {
  test: String
}

Then a client sends a request to fetch this field:

# query
query MyQuery {
  test
}

When this client query reaches the GraphQL server, it looks at the top-level fields. Here, the test field of the Query type is the topmost one, so it will resolve that first.

Resolving the top-level field

That means a resolver is called, it gets some information about the query (such as the arguments), and provides a text response. What exactly that resolver does is up to the implementation, the only important point is that it must adhere to the type system: since the schema defines a String result, the resolver must return a String.

Nested fields

When a top-level field (a query or a mutation) returns a complex type, GraphQL needs to resolve the fields of the inner type too. For example, a query might return a User that has its own fields:

type User {
  username: String!
  email: String
  bio: String
}

type Query {
  user(username: String!): User
}

A query can define what inner fields it needs:

# query
query MyQuery {
  user(username: "user1") {
    username
    bio
    email
  }
}
The GraphQL backend recursively resolves all fields in the response

There is more, but you've reached the end of this preview
Read this and all other chapters in full and get lifetime access to:
  • all future updates
  • full web-based access
  • PDF and Epub versions