There are couple of places in GraphQL where resolving a Type
is required and not just a field
in a Type
.
The Backend API -
/users
- list of users - minimal info - name, id/users/:id
- detailed user info/foo
- returns a field owner which is a UserIDBuilding a schema to execute the following query
query a {
users {
age # some detail info
}
foo {
owner {
location # some detail info
}
}
}
and the schema can be as follows -
type Query {
users: [User]
foo: Foo
}
type Foo {
owner: User
}
type User {
id: ID
age: Int
location: String
}
The resolvers in the above schema would need to contain / handle the user details info fetching call in 2 different places. 1. List of users - Query.users
and 2. Query.foo.owner
. And one has to remember to handle this type where you only have a user ID to convert it to actual User.
As of this writing, GraphQL supports resolveType
on Interface
and Union
. It is not possible to specify a resolver for an entire Type
- only a field
in a Type
can have a resolver. So if it's possible to resolve a type in GraphQL, this would make it simpler to implement.
Since only fields in a Type can be resolved, it is possible to create an extra type
and maintain the handling of this field in the Type in the resolvers and is in one location. But now, the query is deeper than before by 1 level.
query b {
users {
details {
age
}
}
foo {
owner {
details {
location
}
}
}
}
Since a Type
cannot be resolved in GraphQL, enums
face the same issue. When you have a special character in an API response and the field is an ENUM, you either remember to handle it in all the places where this enum is used or you create an extra type to represent this enum.
I have created a minimal repro for all these cases with ApolloGraphQL - https://github.com/boopathi/graphql-test-1
Resolver is a collection of functions that generate response for a GraphQL query. In simple terms, a resolver acts as a GraphQL query handler. Every resolver function in a GraphQL schema accepts four positional arguments as given below − fieldName:(root, args, context, info) => { result }
The __typename field returns the object type's name as a String (e.g., Book or Author ). GraphQL clients use an object's __typename for many purposes, such as to determine which type was returned by a field that can return multiple types (i.e., a union or interface).
You're right - this is definitely something in GraphQL that is a bit odd. Essentially, because of the way resolvers work, it's the type that you came from and not the type that you're going to that is responsible for fetching the right data.
There are pros and cons to this approach. You could definitely imagine an implementation of GraphQL where the parent object simply returns an ID, and then you have one resolver for each type that knows how to fetch the details. I think that would definitely be better for some cases.
Here's how we currently suggest structuring the code to avoid this kind of coupling:
To achieve a poor man's dependency injection we put these onto the context
of the server. When you put it all together, it looks like this:
Schema:
# Information about a GitHub repository submitted to GitHunt
type Entry {
# Information about the repository from GitHub
repository: Repository!
# The GitHub user who submitted this entry
postedBy: User!
...
Resolvers:
export const resolvers = {
Entry: {
repository({ repository_name }, _, context) {
return context.Repositories.getByFullName(repository_name);
},
postedBy({ posted_by }, _, context) {
return context.Users.getByLogin(posted_by);
},
...
You can see this in the context of a whole server in the GitHunt-API example app.
Basically this approach uses the resolvers as a thin wrapper that calls the underlying business logic, almost like a router. This is consistent with the current literature about servers at Facebook and otherwise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With