Open
Description
As projects like Relay have shown, it's relatively common to repeat the same generic structures of types multiple times within a project. In the case of Relay, I'm talking about Connections.
The GraphQL definition language already has explicit support for one particular form of generic type, arrays:
type Foo {
id: ID!
bars: [Bar]
}
I'd like to start discussion about being able to do something similar for user-defined structures:
generic ConnectionEdge<T> {
node: T
cursor: String
}
generic Connection<T> {
edges: ConnectionEdge<T>
pageInfo: PageInfo
}
type Foo {
id: ID!
bars: Connection<Bar>
}
The overall goal is to reduce the amount of boilerplate in creating schemas with repetitive structures.
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
leebyron commentedon Jul 2, 2016
Thanks for proposing this! During the GraphQL redesign last year, we actually considered adding this!
There are two issues that seemed to result in more complication than would be worth the value we would get from this feature, but I'm curious what you think or if you have ideas:
ConnectionEdge
type and look at its fields, what will thetype
ofnode
be? The best answer we could come up with was to changeField.type
fromType
toType | GenericParameter
which is a bit of a bummer as it makes working with the introspection API more complicated. We could also expandType
to include the possibility of defining a generic param itself. Either way, it also has some rippling effects on the difficulty of implementing GraphQL, which would need to track type parameter context throughout most of it's operations.__typename
respond with? What should{ bars { __typename } }
return? This one is pretty tricky.{ "bars": { "__typename": "Connection" } }
? That describes the type, but you're missing info about the type parameter, that that ok?{ "bars": { "__typename": "Connection<Bar>" } }
Is also problematic as now to use the__typename
field you need to be able to parse it. That also adds some overhead if you were hoping to use it as a lookup key in a list of all the types you know about.Not to say these problems doom this proposal, but they're pretty challenging.
Another thing we considered is how common type generics would actually be in most GraphQL schema. We struggled to come up with more than just Connections. It seemed like over-generalization to add all this additional complexity into GraphQL just to make writing Connections slightly nicer. I think if there were many other compelling examples that it could motivate revisiting.
AndrewIngram commentedon Jul 2, 2016
You're right about the number of use cases being relatively small, i'll need to think on that point.
To be honest, this feels like sugar for developers of schemas rather than clients. In the simplest case, i'd just expect the introspection result to be the same as it is now, i.e the generics get de-sugared. To that end, it could just be something that parsers of the schema definition language end up supporting, but it's up to library authors how to handle the generated AST.
In graphql-js land, there are numerous examples of libraries (apollo-server, my own graphql-helpers, and a few others I can't remember) which use the parser provided to vastly simplify the process of building schemas (having done it both ways, I'd say it's pretty close to an order of magnitude more productive), and i'd personally be happy to add additional support for tokens related to generics to my library.
However, it does feel weird supporting a syntax that's not actually reflected in the final generated schema, so i'm unsure about this approach.
Qard commentedon Mar 20, 2017
I really wish something like this would be reconsidered. Connections may just be a single use-case, but it's a big one, in my opinion. The length of my current schema would cut in half with generics.
Currently I have 24 copies of basically this:
That's nearly 200 lines of code that could easily be expressed in 8 lines of generics. I'm seriously considering writing my own preprocessor just to hack on my own generics capability...
stubailo commentedon Mar 20, 2017
Hmm, in graphql-tools you could do something like:
Is there something the syntax could have that would be better than that JS-style approach?
Qard commentedon Mar 20, 2017
And what if you're not using JS? 😞
I want my schema to be pure graphql schema language so it doesn't need preprocessing.
stubailo commentedon Mar 20, 2017
Yeah I definitely sympathize. I guess the real question is, is the generic thing just something for the server to be written more conveniently, or does the client somehow know that these types/fields are generic and acts accordingly?
If the client doesn't know, then I feel like it should be a preprocessor feature or a macro thing. The spec is all about the contract between client and server IMO.
However, there are definitely implementations for generics where the client could actually take advantage of knowledge that something is a generic thing. For example, in the connection example, there's no way to make an interface that says that a
TypeXConnectionEdge
should have anode
of typeX
, so you can't really enforce that without relying on conventions.Perhaps this could be done as some sort of intersection of interfaces and type modifiers? So basically, it's a way of creating your own type modifiers - if you squint hard enough,
[ ... ]
and!
are kind of likeList<T>
andNonNull<T>
.So building on that, in introspection:
You could get:
Perhaps this should come with a change of
kind: "LIST"
tokind: "GENERIC", name: "LIST"
.Qard commentedon Mar 20, 2017
I mean, it seems hugely valuable for the client to understand generic concepts too, but that could probably be expressed as simply differently named types, since the client doesn't generally need to be too concerned about the actual name of types so much as what is in them. It seems to me like it'd be really valuable to be able to, in a type-safe way, express concepts like pagination while retaining the safety of recognizing that a given type encapsulates objects of a specific type. Without generics or preprocessing you can sorta-kinda do this with unions, but then you're throwing away the promise that all items in a list are of a specific type...
stubailo commentedon Mar 20, 2017
I guess in my mind, whether or not the client should worry about it is a very important factor in determining whether it should be in the spec or simply live as some server-side library tooling.
mike-marcacci commentedon May 9, 2017
Hi everybody! I've definitely been looking to solve the Connection use-case here, and another similar case specific to my project. I have a slightly different strategy which I've laid out in #295, which is a much smaller change, and in no way mutually exclusive with this proposal.
Basically, if an interface were able to explicitly implement another interface, the client would be aware of the hierarchy. That is, it would be provided similar context to what generics might provide, but without adding new semantics to the protocol.
This wouldn't solve the issue of verbosity within the schema, but leverage the existing types to convey "generic" type information to the client. In this way, a preprocessor might be able to compile generics into hierarchal interfaces, achieving both goals here.
AndrewIngram commentedon May 10, 2017
Given that we now have at least one authoring-only syntax extension, i.e.
extend type
, is it worth reconsidering generics in the same light?stubailo commentedon May 11, 2017
Hmmm, that's true -
extend type
is not accessible through introspection at all, I didn't think about that.AlecAivazis commentedon May 18, 2017
One use case I have run into for generics is having something resembling
Maybe<T>
to handle error messages. I'd like to do so without having to mess with the network layer and introduce some sort of global state, or refer to a disjointed part of the response. Currently, I am defining a separate union type for each object (ieMaybeUser
is aUser | Error
) but it would be nice to be able to do this as simply asMaybe<User>
and define the structure once.An alternative to avoid the extra complexity of the generic union would be something as simple as
xialvjun commentedon Jul 7, 2017
Another use case is
crypticmind commentedon Aug 5, 2017
generic Maybe<T> { ... }
andgeneric Pagination<T> { ... }
look great to me, though I'd drop thegeneric
keyword as it seems redundant by the use of angle brackets.60 remaining items
dan-turner commentedon Oct 1, 2022
Any progress or developments on this?
rgolea commentedon Mar 7, 2023
I can't wait for this to become a reality. It's the main reason I had to move away from graphql as it become so hard to maintain and work with. I had to remember all these properties I had to distribute everywhere. And yes, implements works pretty well but at the end of the day I had to copy all the properties around.
However, thank you all for the awesome contribution. I can't stress enough how much I love graphql.
DeveloperTheExplorer commentedon Mar 9, 2023
It has been almost 7 years. Any updates? This is very much needed for schema/resolver definitions. Almost all endpoints need this whenever pagination is involved. And almost all systems implement some sort of pagination/cursor.
akomm commentedon Mar 10, 2023
Going schema DSL in the graphql
implementations
(libs, not gql itself) is one of the biggest mistakes IMO. You can see it by all those "solutions" cooked around it by now, to fix problem A, B, C and problems still being present to this day. Whoever stayed on the code side without all the mess around it, has much less problems with graphql.Like always, I try out new tech and evaluate it. It feels tempting at first glance, but the price vs. benefit is hugely disproportional.
You can have quite clean schema definitions also in code. Without all the primitive constructors provided by the impl. libraries. And you can eliminate repetition that you try to solve with generics in schema, just by using code.
The examples I've seen so far here, that should prove the code approach without generics leads to bad naming, feels artificial. Most of the time its a matter of
Change<String>
vs.ChangeString
. The example variants with nullability are not really useful. To talk about whether its a problem or not needs some real world example to see whether its even needed for the type in the specific case to have the nullability encoded in the name this way. I can just imagine RL examples for that where you don't actually need it, unless you make some weird design decision. If you want to avoid collision in name, the first question is why do you have those two variants, what is the intent of the data structure you try to describe and isn't the nullability something that is rather implied from a different type name as a base (OptionalChange<T>
vs.Change<T>
) instead.I'm not saying there is no problem, but just that the examples I've seen here so far are IMO to artificial to be convincing.
jamietdavidson commentedon Sep 7, 2023
Would like to reup this. Seems relevant and I stumble into Enum / Interface related issues every few months that would be solved by this. It's kind of the last missing piece to making GraphQL not leaving something to be desired, IMO.
n614cd commentedon Jan 10, 2024
Is there a specific blocker to making this happen?
Resources to push it through? Some unsolved problem?
varvay commentedon Feb 17, 2024
At first I was excited to the idea about generic type support in GraphQL, since it might introduces high degree of Intuitiveness for developer to translate the GraphQL schema into code implementation in various language and framework. It's the same concept as having scalar data types as much as possible to match all the possible implementor languages e.g., think how intuitive is it to implement the schema when it supports data type like hashmap, JSON node, etc.
But I've been thinking to myself about this and end up by accepting the current GraphQL specification without generic type support. The question i've been asking myself are,
So I started from the mindset that GraphQL is a specification used as contract between frontend and backend on what data they will exchanging and how are they structured. There will be complication introduced with generic type implementation on these information received by the client, for example how does the client knows the structure of the object defined as generic? There must be an information to communicate it right? you'll ended up by sending some kind of metadata, which is redundant to
__typename
.The next question is,
I don't think so. The client's frameworks will still need to implement the data resolution abstraction and the quality of the information received by the client doesn't necessarily increased since the only additional information are "this object is generic and it might be in type A, B or C", which is communicated already through the usage of union type definition.
I think it does fulfill them with two possible solutions on the table,
Discussing the 2nd solution further,
Such query is resolvable in the code implementation by mapping the type based on the
__typename
metadata, for example like using discriminated union in typescript. I also still consider the schema definition verbosity is acceptable since the only verbosity came fromPageable
union type definition, since for new type extending pagination functionality will goes to this list. But the tradeoff with strong data typing and information quality build upon the contract are reasonable.Or what you really asking is some kind of no-type data and omitting the type enforcement feature? I think this is oppose to the main goal of GraphQL itself.
Finally, based on those thinking, I concluded for myself that in use cases I've seen so far, the only benefit I'm going after is syntactic sugar. This might also be your case. In my opinion, If there are any work should be done regarding this, they should be on the client side (developer and implementor framework).
mathroc commentedon Feb 19, 2024
no, if you have:
then there is no doubt about what
posts.nodes
contains inquery { posts { nodes {}}}
This is not what generics do
and it's probably the same misunderstanding, but if you use generics in your example, it becomes:
And there's definitely readability improvements as-well as better typings than in the previous version (you know that
query.getPosts.data
will always be of type[Post]
, not[Post|Profile|Tag|Setting]
nort3x commentedon May 10, 2024
@mathroc
inspired by how C++ resolve templates, we can preprocess schema and produce this pipe:
input schema:
processed schema:
notice that we can't really mangle the outcome because it should be follow-able by consumer of the API
another issue is that the consumer should also use the same processor or re-implement them one by one
i used this in a small passion project and wasn't really an issue, but i understand the complication and integrity problems it could bring into the specification...
8 years is alot i think graphql specification should stay as is and task these improvements to it's successors