Reputation: 75
I have some struggles with writing an API in graphql.
Every response in my api should look somewhat the same. So ideally this would be the graphql type:
type Response {
success
data {
... always different
}
errors {
path
message
}
}
But because the data field in here is always different. Every Mutation/Query should have it's own response type (if i'm understanding graphql correctly).
So for Login this is the type I'm creating with a transformer function:
type LoginResponse {
success
data {
user
token
}
errors {
path
message
}
}
Now in my front-end, I want to use the following fragment, because these properties are always present in every response.
fragment Response on LoginResponse {
success
errors {
path
message
}
}
So the problem I have with this is already shown here, with a fragment you also define it's parent type. So I have to create as many seperate fragments as seperate response types.
Has someone maybe already struggled with this or is there a best practice for this I'm not seeing
Upvotes: 1
Views: 1921
Reputation: 84687
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:
data
and errors
properties.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.errors
array and inside data.errors
. That also means your client needs to look for errors in two locations to do proper error handling.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.
Upvotes: 3