In this article, we’ll dive in some more advanced concepts for query types and resolvers. If you are still new, or would like to review the basics, check out the Apollo 🚀 and GraphQL article.
Query Resolvers
The resolvers are at the root of the query mechanism. A resolver is a function that’s responsible for populating the data for a single field in your schema.
GraphQL Query books
The GraphQL schema is a graph connecting types (which have fields) together.
The most obvious type is Query
which contains multiple fields which would correspond to the base query.
type Query {
books: [Book!]
}
Resolver structure
So nothing new, the resolver for books
will be an imported method for simplicity:
import { books } from './resolvers/Query/books';
const Resolvers = {
Query: {
books
}
}
You can see that the field books has been mapped to a books
folder.
When querying, the resolver will hit the Query
type then look for the books
function.
Type Resolvers
GraphQL Query with more types
Now let’s say we define the Book object having an author
field of type Author
.
type Book {
title: String!
author: Author!
}
type Author {
name: String!
}
Now we can also add a resolver on Book
so that the author
field when queried will be called.
For example with a query:
query {
books {
title
author {
name
}
}
}
This will get all the books, their title and their author’s name.
For the sake of the example, the books and the authors are two separate data entities.
A new resolver on a field is only useful when you need to apply some logic, use some input
to find the correct value.
Implementation of author
resolver
Now when querying the fields in Book, we want to use the specific resolver we’re assigning to that field.
The new resolver would look like:
import { author } from './resolvers/Book/author';
const Resolvers = {
Query: { books },
Book: {
author
}
}
You can see that we added a new resolver for the type Book
on the field author
.
Which means the author
function will be called when querying the author of a book.
Let’s see how the resolver function for author
will look like:
export function author(parent: Book, _: any, context: any) {
return context.authorDataSource.find(author => author.books.includes(parent.title));
}
The first argument is the parent, which in this case is a Book
, since you always query the author
from a book.
In the context, you usually have everything you need like a dataSource which is an adapter to access
your data (e.g. from a database).
In this case we could have all the others that are stored with a list of books title in author.books
.
Filtered fields
GraphQL authors
query
For example having a new authors
query to query all the authors and their books like:
type Query {
authors: [Authors!]
}
type Author {
name(name: String): String!
books: [Book!]!
}
In here, we put an input on one of the field within the Author
type that can be queried.
Meaning we can filter for author’s name (or part of the name like firstname/lastname).
query {
authors {
name(name: "Edgar")
}
}
It would yield all authors whose name contains “Edgar”.
Implementation of the name
resolver
We would have a name
resolver in the Author
type that will handle the arguments and the logic.
Here is an updated view of the resolvers:
import { authors } from './resolvers/Query/authors';
import { name } from './resolvers/Author/name';
const Resolvers = {
Query: { books, authors },
Book: { author },
Author: {
name
}
}
In this case the name
resolver will look like:
export function author(_: any, { name }: { name: string }, context: any) {
return context.authorDataSource.find(author => author.name.includes(name));
}
We don’t use the information of the parent, instead we use the argument that was passed is an object containing the name of the author to filter on.
Circular resolver
Adding the books
field to the Author
type we created a circular resolver in our graph.
Circular dependency can become a vulnerability,
while not overly recommended because there’s no default protection for it,
it is to be expected when we are talking about Graphs.
You could have an endless nesting which can break your application:
query {
books {
title
author {
title
books {
title
author {
...
}
}
}
}
}
In this case, you would need a depth limiter that will throw an error when you go on too many levels in your query. Here there are already more than 4 levels in depth.
There are more caveats to look after in the Apollo Federationdocumentation. For this particular problem, you can use the graphql-depth-limit library or implement your own depth limiter to fix this issue.