GraphQL: How to reuse same type for query and mutation?

Unfortunately, a type cannot be used in place of an input, and an input cannot be used in place of a type. This is by design. From the official specification:

Fields can define arguments that the client passes up with the query,
to configure their behavior. These inputs can be Strings or Enums, but
they sometimes need to be more complex than this.

The Object type defined above is inappropriate for re‐use here,
because Objects can contain fields that express circular references or
references to interfaces and unions, neither of which is appropriate
for use as an input argument. For this reason, input objects have a
separate type in the system.

What’s more, fields on a GraphQLObjectType can have args and a resolve function, while those on an GraphQLInputObjectType do not (but they do have default values, which are not available to the former).

It also makes sense to keep them separate from an implementation standpoint. Simple schemas are likely to just map fields to columns in some table. However, in real-world applications, it’s much more likely that you’ll have derived fields that don’t map to any one column (and would not be appropriate to use inside an input).

It’s also likely you’ll only want some fields to be used as input (if you’re adding a user, for example, a client shouldn’t send you an id; this should probably be generated by the db when the user is added). Likewise, you may not want to expose every field that’s used as input to the client, only those they actually need.

If nothing else, your use of non-null will probably be different between an input and a returned type.

That said, there is something of a workaround. In graphql-js, at least. If you declare your schema programatically, you could separately define an Object with your set of fields, and then set your fields property for both your User and UserInput objects. Alternatively, if you’re defining your schema declaratively (like in your example), you could use template literals like this:

const userFields = `
  id: ID!
  login: String!
  name: String
`
const schema = `
  type User {
    ${userFields}
  }
  type UserInput {
    ${userFields}
  }
`

Heck, if you wanted to, you could even iterate over every defined type and programatically create a matching input type. However, IMO, the effort in implementing any of these workarounds is probably not worth it when you consider the cost in flexibility. Bite the bullet, put some thought into what you actually need as an input to that mutation and specify only the fields you need.

Leave a Comment