GraphQL Operations’ Conventions

When GraphQL APIs start to grow, conventions are needed for both your team developing the API, and for your consumers, the front-end and your clients.

There’s no official standard on name conventions so I’ll show some options I’ve seen out there, and you can choose the one that fits your case the best!

Queries

Queries are pretty similar among the APIs, they refer to the first-class citizens, the main entities in the API or the aggregate roots of the domain; or actions that don’t mutate the data, like search .

Example queries (taken from Github’s API):

The pattern would be something like:

Entity (...parameters) : Result Action (...parameters) : Result

Note: input parameters aren’t generally used in popular APIs but they can be used without any problem. If you think that the parameters of your query can be reused or will grow, go for them.

Mutations

Firstly, mutation parameters and results usually follow the input/payload pattern as follows:

# mutation (InputType) : PayloadType updatePersonAddress(UpdatePersonAddressInput): UpdatePersonAddressPayload

Secondly, Specificity is very important. Make mutations as specific as possible. Mutations should represent semantic actions that might be taken by the user whenever possible.

Thirdly, name conventions. I’ve seen 4 approaches, these are the patterns (names made up by me):

1. VerbNoun

Pattern: <verb><noun>

Examples:

setRepositoryOwnerEmail

setRepositoryOwnerName

updatePersonAddress

This is supposedly how Facebook uses (see Lee Byron’s comment) and what Apollo recommends.

2. Namespaces

Pattern: <namespace><action>

Examples:

repositorySetOwnerEmail

repositorySetOwnerName

personUpdateAddress

Namespaces has been discussed for a while now for the spec in this GraphQL official Namespaces RFC, but they haven’t been convincing the right people yet. These are some of the pro’s/con’s seen in the discussion.

PROs

They

Provide a sense of organization to the API, helping on the discoverability of the actions

Avoid name clashes

Easier stitching of different schemas (see Cons for the dark side of this)

But we also mentioned some possible risks that should be avoided.

CONs

It might help stitch different schemas/sources without any API unification, it would be just gluing downstream APIs, each with its own namespace. This triggers a couple of questions you’ll need to answer: Should your API represent one domain or more? (one is usually the right answer) Are you leaking implementation details to the API? (no is usually the right answer)

It could be used to version parts the API (e.g. namespaceV2search ), which isn’t the graphql way; namespacing is not versioning

), which isn’t the graphql way; namespacing is not versioning It could be used to duplicate types, like Accounting/Person / Marketing/Person . This can break the single domain of the API or can allow bad DDD

/ . This can break the single domain of the API or can allow bad DDD Reuse of types is encouraged to simplify the API and avoid the proliferation of same but similar type of types/operations. NS leaves the door open for repetition. Look for reuse opportunities and evaluate if they make sense

Just to be clear, this is a valid approach, you just need make sure it doesn’t lead to any of the cons.

3. Object-Oriented

Pattern: : <object><method>

Examples:

repositoryOwnerSetEmail

repositoryOwnerSetName

personUpdateAddress

This is similar to how you would call a function/method in many programming languages.

You can see that the 3rd example is the same as the “Namespaces” approach. This happens because person naturally becomes the namespace. But this happens only on 1st root level entities. You can see that the 2nd example differs between approach #2 and #3, as owner is nested inside repository .

4. Shopify’s approach

Pattern: <target><action>

Examples:

repositoryOwnerEmailSet

repositoryOwnerNameSet

personAddressUpdate

This is taken from Shopify’s GraphQL API. You can check one of the leads of Shopify’s API Team explaining some of their rules designing their API in this video.

Honorable mention

This is another option but it’s not a convention but a different design practice. It’s based on this Oleg Ilyenko’s article.

There’s 1 mutation per aggregate root update and inside that input object, each action as “inner” mutation.

type Mutation { updateDiscountCode ( id : String !, version : Long !, actions : [ DiscountCodeUpdateAction !]!): DiscountCode } input DiscountCodeUpdateAction { setName : SetDiscountCodeName setDescription : SetDiscountCodeDescription setCartPredicate : SetDiscountCodeCartPredicate setCustomType : SetDiscountCodeCustomType setCustomField : SetDiscountCodeCustomField # ... }

With this you get less first-class mutations and you’re able to send many mutations in one. At the first level you would see the Create/Update/Delete mutations where update has all the specific “nested mutations”.

I encourage you to read the article to fully understand Oleg’s idea and analyze if it makes sense for you.

What about Subscriptions?

I haven’t seen subscriptions in the public APIs, maybe they are hidden, part of the applications’ features. But I would go with either of these two approaches:

1. Noun

Here you subscribe to an event in its noun form, like DeploymentProgress .

This event can be a union of multiple events like DeploymentStart / DeploymentProgress / DeploymentComplete for example. If not you will end up having one big type with all optional fields. Or go to the 2nd approach.

2. Past Event

Here you subscribe to an event in its past form, like DeploymentProgressed or DeploymentCompleted (the types in the previous approach union).

Thiss way is more granular and you don’t have to resolve what type is coming back from the server, but if these subscriptions are talking about the same deployment you need to “coordinate” all 3 subscriptions in some way.

Regarding input/payload types, I would follow the same conventions as mutations.

Hope it’s helpful, share if it is! Cheers!