In general, when you have a field that could resolve to one of a number of types, you can utilize a Union. If those types share one or more fields, you may want to utilize an Interface instead.
A common pattern you see in schemas is the idea of a Node
interface. You could have a query to fetch a node by id, for example:
type Query {
node(id: ID!): Node
}
interface Node {
id: ID!
}
type Foo implements Node {
id: ID!
foo: String!
}
type Bar implements Node {
id: ID!
bar: Int!
}
Here, a Node
could be either Foo
or a Bar
, so if we were to write a fragment for Node
, it might look something like this:
fragment NodeFields on Node {
id # id is part of the interface itself
... on Bar {
bar # fields specific to Bar
}
... on Foo {
foo # fields specific to Foo
}
}
If you don't have shared fields, you can utilize a Union instead to the same effect:
union SomeUnion = Foo | Bar
So, to alleviate some of the repetition in your front-end code, you could make each of your Result
types an interface, or better yet, have a single Result
type with data
being a union. Unfortunately, neither Interfaces or Unions work with Scalars or Lists, which complicates things if data
is supposed to be a Scalar or List for some queries.
At the end of the day, though, it's probably not advisable that you structure your schema this way in the first place. There's a number good reasons to avoid this kind of structure:
- GraphQL already returns your query result as a JSON object with
data
and errors
properties.
- Returning errors inside of the GraphQL
data
will require additional logic to capture and format the errors, as opposed to being able to just throw an error anywhere and have GraphQL handle the error reporting for you.
- You won't be able to capture validation errors, so you'll potentially end up with errors in two places -- inside the
errors
array and inside data.errors
. That also means your client needs to look for errors in two locations to do proper error handling.
- GraphQL is specifically design to allow a response to be partially resolved. That means even if some parts of a response errored out and failed to resolve, others may still be resolved and returned as part of the response. That means the concept of a response being "successful" doesn't really apply in GraphQL. If you absolutely need a
success
field, it would be far better to utilize something like formatResponse
to add it to the response object after the query resolves.
It will make things significantly simpler to stick with convention, and structure your schema along these lines:
type Query {
login: LoginResponse
}
type LoginResponse {
token: String
user: User
}
The actual response will still include data
and errors
:
{
"data": {
"login": {
"token": "",
}
},
"errors": []
}
If you even need to use fragments, you will still need one fragment per type, but there will be significantly less repetition between fragments.