Return incomplete shapes from resolver with GraphQL Code Generator

1k views Asked by At

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?

2

There are 2 answers

0
TLadd On

When I have these sorts of scenarios, I make the extra field nullable (replace extra: UserExtra! with extra: 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 with extra set to null instead of throwing the other user data away due to extra being null and violating the schema type. The Non-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 for extra 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.

0
zhangzhimin On

There is a configure option called mapper. Basically you can specifiy (overwrite) the return type of your resolver to better match your actual return type from your code.

Actually GraphQL in general allows you to return any kind of data in resolvers. So that's why we have mappers to let specify the types for those.

For more detail https://github.com/dotansimha/graphql-code-generator/discussions/4030