Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

graphql

Let's say I have a query defined liked this:

type Query {
  # The basic me query
  getUser(id:Int): [User]
}

type User {
  id: ID!
  login: String!
  name: String    
}

But now I need to have a mutation to add a user. My intuition would be to to something like this:

type Mutation {  
  addUser(newUser: User): [User]
}

But it is not working because a mutation cannot use "query type". It need to use "input type". I know that in this example, it is not really complicated to do, but if user was a really complex type with many sub-type used. How can I do that? Is there a was to re-use a type as a mutation argument?

like image 317
tleveque Avatar asked Sep 11 '17 14:09

tleveque


1 Answers

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.

like image 126
Daniel Rearden Avatar answered Nov 29 '22 00:11

Daniel Rearden