Structuring Queries
How to structure your code is always a matter of a personal taste but I think the collocation of queries and components is a big strength of GraphQL.
For queries I took a lot of inspiration from Relay Modern and the solution looks very close to what you described in the code. Right now as the project becomes bigger and we want to generate Flow type definitions for our queries, putting them into separate files next to the component files is also an option. This will be very similar to CSS-modules.
Structuring Mutations
When it comes to mutations it often gets much harder to find a good place for them. Mutations need to be called on events far down the component tree and often change the state of the application in multiple states of the app. In this case you want the caller to be unaware of the data consumers. Using fragments might seem like an easy answer. The mutation would just include all fragments that are defined for a specific type. While the mutation now does not need to know which fields are required it needs to know who requires fields on the type. I want to point out two slightly different approaches that you can use to base your design on.
Global Mutations: The Relay Approach
In Relay Modern Mutations are basically global operations, that can be triggered by any component. This approach is not to bad since most mutations are only written once and thanks to variables are very reusable. They operate on one global state and don't care about which UI part consumes the update. When defining a mutation result you should usually query the properties that might have changed by the mutation instead of all the properties that are required by other components (through fragments). E.g. the mutation likeComment(id: ID!)
should probably query for the likeCount
and likes
field on comment and not care much if any component uses the field at all or what other fields components require on Comment
. This approach gets a bit more difficult when you have to update other queries or fields. the mutation createComment(comment: CreateCommentInput)
might want to write to the root query object's comments
field. This is where Relays structure of nodes and edges comes in handy. You can learn more about Relay updates here.
# A reusable likeComment mutation
mutation likeComment($id: ID!) {
likeComment(id: $id) {
comment {
id
likeCount
likes {
id
liker {
id
name
}
}
}
}
}
Unfortunately we cannot answer one question: How far should we go? Do I need the names of the people liking the comments or does the component simply display a number of likes?
Mutations in Query Container
Not all GraphQL APIs are structured the Relay way. Furthermore Apollo binds mutations to the store similar to Redux action creators. My current approach is to have mutations on the same level as queries and then passing them down. This way you can access the children's fragments and use them in the mutations if needed. In your example the CommentListItem
component might display a like button. It would define a fragment for the data dependencies, prop types according to the fragment and a function prop type likeComment: (id: string) => Promise<any>
. This prop type would be passed through to the query container that wraps the CommentsPage
in a query and mutation.
Summary
You can use both approaches with Apollo. A global mutations
folder can contain mutations that can be used anywhere. You can then directly bind the mutations to the components that need them. One benefit is that e.g. in the likeComment
example the variable id
can be directly derived from the components props and does not need to be bound within the component itself. Alternatively you can pass mutations through from you query components. This gives you a broader overview of the consumers of data. In the CommentsPage
it can be easier to decide what needs to be updated when a mutation completed.
Let me know what you think in the comments!