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.
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 a constrained version of Javascript 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).
Resolvers provide values for fields.
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.
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.