Graphql-js: Using Input types inside compound Object types.

Created on 22 Nov 2016  路  9Comments  路  Source: graphql/graphql-js

Suppose I have a filter type that I use to constrain a query:

input Filter {
  type: String
  labels: [String]
  text: String
}

And I want to create a set of folders that contain the filter definitions; e.g.,

type Folder {
  name: String
  filter: Filter
}

This seems perfectly reasonable, but can't be achieved since Input types can't include Output types.

@leebyron, you don't really explain WHY in [https://github.com/graphql/graphql-js/issues/423]. In your own example:

input Location {
  lat: Float
  lon: Float
}

Should we create pairs of each basic type (Location and InputLocation; Address, InputAddress; TelephoneNumber, InputTelephoneNumber, Name, InputName, etc.)?

Given the class vs struct analogy. It seems that structs (input types) could reasonable by included in class (output type) definitions?

Related Apollo discussion: https://github.com/apollostack/graphql-tools/issues/179

question

Most helpful comment

The primary reason is that under the hood these are two very different structures - one is responsible for taking input values and coercing them into server-side values, the other is responsible for fetching information from a server. If we were to allow input types as output types, they would have to implement both of these structures.

In addition to this, the input and output types are very often not the same with regards to nullability, which behaves differently in input and output contexts. For inputs, non-null means that it's required to be provided whereas for outputs, non-null means that it's guaranteed to provide a value if queried. A really common scenario for wanting to reuse input types is when building a CRUD style API where you want to provide fields to update in a mutation. In these kinds of APIs it's really common for a type to have non-null (guaranteed to exist) fields but shouldn't be required to be provided during a mutation. For these kinds of APIs, there are often types such as Address and AddressUpdate for the output and input types respectively.

Finally there's just the potential user confusion of defining an input type, and then using it as an output type - given the differences that input types have, this could make GraphQL more difficult to understand, and we've strived to keep things easier to understand even if that sometimes comes at the cost of convenience.

All 9 comments

The primary reason is that under the hood these are two very different structures - one is responsible for taking input values and coercing them into server-side values, the other is responsible for fetching information from a server. If we were to allow input types as output types, they would have to implement both of these structures.

In addition to this, the input and output types are very often not the same with regards to nullability, which behaves differently in input and output contexts. For inputs, non-null means that it's required to be provided whereas for outputs, non-null means that it's guaranteed to provide a value if queried. A really common scenario for wanting to reuse input types is when building a CRUD style API where you want to provide fields to update in a mutation. In these kinds of APIs it's really common for a type to have non-null (guaranteed to exist) fields but shouldn't be required to be provided during a mutation. For these kinds of APIs, there are often types such as Address and AddressUpdate for the output and input types respectively.

Finally there's just the potential user confusion of defining an input type, and then using it as an output type - given the differences that input types have, this could make GraphQL more difficult to understand, and we've strived to keep things easier to understand even if that sometimes comes at the cost of convenience.

@leebyron thanks for the response and sorry for the "late" response (just picking this up again now since I've been working around the issue).

I understand the concerns, but it seems that in hindsight, GQL could have been designed to address these (e.g., protocol buffers support similar semantics but don't have this issue.

I have a nested Input type system that defines about 20 types. So I need to redefine all 20 types (and modify them each time they grow/change) just so that I can return the same JSON data structure from my requests. Do you see that this can be brittle -- is this considered an edge case? The only practical solution I can see is to write yet-another-pre-compiler, or as I'm currently doing, return data as a JSON string and parse this on the client -- all three approaches seem quit ugly.

Protocol buffers are super different because fields don't have arguments. While protobuf to my knowledge just describes the shape of an object graphql object types can do a lot more. Input types are much simpler and just describe the shape.

I think the best solution here is to do some code generation on the server to generate two types, one output and one input?

Once again, thanks again for the quick response.

Re protos, I think you're probably right; you can define services (https://developers.google.com/protocol-buffers/docs/proto#services), but I don't think you can easily inline them inside types -- although I think that's a design choice -- and I'm suggesting that's possibly the same here?

````
type MyMutation {
foo(input: MyComplexInputType!): MyComplexType!
}

input MyComplexInputType { foo: Foo! }
type MyComplexType { foo: Foo! }

input FooInput { bar: BarInput! }
type Foo { bar: Bar! }

input BarInput { title: String! }
type Bar { title: String! }

// Turtles all the way down...
````

How could I do this on the server? I need definitions I can use in the GQL files. So I'd have to write another DSL? What I'm doing now:

````
type MyMutation {
foo(input: MyComplexInputType!): String! // Return JSON and lose nice type checking.
}

input MyComplexInputType { foo: Foo! }
input FooInput { bar: BarInput! }
input BarInput { title: String! }
````

Has there been any further development on this discussion? It seems like it has been agreed upon by the community to keep input output types separate for all intents and purposes.

This drives me to question could the type definitions for both scenarios be structured so that they are easily maintainable? How does that look like at scale?

@leebyron - you say the main reason for input types is to disambiguate required fields. Could this problem be solved by having a different operator to specify if a field is required for input or output?

Or having type definitions be input and out put by default, but have some operator like:

type MyType {
  fieldOnInputAndOutput: String!
  > {
    fieldOnlyOnOutput
  }
  < {
    fieldOnlyOnInput
  }
}

Agreed. Separate input and output types seems to be a leaky implementation detail: for any graph that is possible to represent as an input type, I think the compiler should simply understand based on the context in use (mutation). Separate types causes additional user friction, which is significant for something as unique as GraphQL, and which may lead to maintainability issues, as it seems I have to duplicate portions of my schema between input and output, unless I just haven't found the appropriate feature, quite possible.

Like other languages, as a GraphQL newbie, to have reusable/consistent fields i.e., names/data types for input/output especially for large schemas, it appears as if we need another "data/field type" OR at the least "interface type" that just has scalar/object attributes - with no GraphQL semantics or meaning - unless other types and inputs extend OR implement them and each add their own customization, eg., input type can add non-null on some fields etc., if needed :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

adriano-di-giovanni picture adriano-di-giovanni  路  3Comments

matthewgertner picture matthewgertner  路  4Comments

galki picture galki  路  3Comments

sudheerj picture sudheerj  路  3Comments

hekike picture hekike  路  4Comments