I use the following convention to let resolvers return partial data, and allow other resolvers complete the missing fields:
type UserExtra {
name: String!
}
type User {
id: ID!
email: String!
extra: UserExtra!
}
type Query {
user(id: ID!): User!
users: [User!]!
}
const getUser = (id: string): { id: string, email: string, extra: { name: string } => fetchUser(id);
// `fetchUsers` only returns `id` and `email`, but not `extra`
const getUsers = (): { id: string, email: string }[] => fetchUsers();
// we can use this function to fetch the extra field for a given user
const getUserExtra = (id: string): { name: string } => fetchUserExtra();
export default {
Query: {
user: (parent, args) => getUser(args.id),
users: () => getUsers(),
},
User: {
// here we fetch the `extra` field anytime an `User` is requested
// in real-life I would check if the query is requesting the `extra`
// field or not, and only fetch the data if requested by the query
extra: (parent) => {
return getUserExtra(parent.id)
},
}
}
The problem I'm having is that GraphQL Code Generator generates a Resolver
type that expects Query#users
to return the complete User
shape, and of course it's not aware of the fact that even though I return a partial shape from Query#users
, thanks to User#extra
the client will end up receiving the expected shape nonetheless.
What's the best approach to handle this case while keeping TS happy?
When I have these sorts of scenarios, I make the
extra
field nullable (replaceextra: UserExtra!
withextra: UserExtra
). There are a number of articles out there on how to treat nullability in your Graphql schema (This and this are two that were influential for me).Presumably, the
extra
fields are separated off in a different resolver because you have to do some sort of additional work to get them like requesting data from another service or data store. If that request ends up failing, it's nice for the schema to declare the type as nullable so the rest of the user data still gets returned withextra
set to null instead of throwing the otheruser
data away due toextra
being null and violating the schema type. TheNon-null fields mean small failures have an outsized impact
in this article does a good job of explaining this issue in detail. The tradeoff is that then your client code needs to check forextra
being null or not, but one could argue that is forcing your client code to consider handling plausible failure cases more gracefully and is a good thing.This change would also fix the original issue you are trying to solve since
extra
will be an optional type in the generated graphql-code-generator types and your main user resolver is not required to return it.