Docs
Launch GraphOS Studio

Namespacing by separation of concerns

schema-design

Most GraphQL APIs provide their capabilities as root-level s of the Query and Mutation types, resulting in a flat structure. For example, the GitHub GraphQL API has approximately 200 of these root-level s! Even with tools like the Apollo , navigating and understanding larger "flat" graphs can be difficult.

To improve the logical organization of our graph's capabilities, we can define namespaces for our root-level s. These are s that in turn define query and s that are all related to a particular concern.

For example, we can define all of the s related to User objects in a UsersMutations namespace object:

type UsersMutations {
create(profile: UserProfileInput!): User!
block(id: ID!): User!
}

We can then define a similar namespace for Comment s:

type CommentsMutations {
create(comment: CommentInput!): Comment!
delete(id: ID!): Comment!
}

Now, both our User and Comment types have an associated create , which is valid because each is defined within a separate namespace type.

Finally, we can add our namespace types as the return values for root-level s of the Mutation type:

type Mutation {
users: UsersMutations!
comments: CommentsMutations!
}

We can use the same pattern for queries that involve User and Comment types:

type UsersQueries {
all: [User!]!
}
type CommentsQueries {
byUser(user: ID!): [Comment!]!
}
# Add a single root-level namespace-type which wraps other queries
type Query {
users: UsersQueries!
comments: CommentsQueries!
}

With our namespaces defined, client s now use a nested format, which provides context on which type is being interacted with:

mutation CreateNewUser($userProfile: UserProfileInput!) {
users {
create(profile: $userProfile) {
id
firstName
lastName
}
}
}
query FetchAllUsers {
users {
all {
id
firstName
lastName
}
}
}

💡 Note that you don’t need to repeat the text user in the names of the UsersQueries type, because we already know all of these s apply to User objects.

Namespaces for serial mutations

Unlike all other s in a GraphQL , the root-level s of the Mutation type must be resolved serially instead of in parallel. This can help prevent two s from interacting with the same data simultaneously, which might cause a race condition.

mutation DoTwoThings {
one {
success
}
# The `two` field is not resolved until after `one` is resolved.
# It is not resolved at all if resolving `one` results in an error.
two {
success
}
}

With namespaces, your s that actually modify data are no longer root-level s (instead, your namespace objects are). Because of this, the s are resolved in parallel. In many systems, this doesn't present an issue (for example, you probably want to use another mechanism in your s to ensure transactional consistency, such as a saga orchestrator).

mutation DoTwoNestedThings(
$createInput: CreateReviewInput!
$deleteInput: DeleteReviewInput!
) {
reviews {
create(input: $createInput) {
success
}
# Is resolved in parallel with `create`
delete(input: $deleteInput) {
success
}
}
}

If you want to guarantee serial execution in a particular , you can use client-side es to create two root s that are resolved serially:

mutation DoTwoNestedThingsInSerial(
$createInput: CreateReviewInput!
$deleteInput: DeleteReviewInput!
) {
a: reviews {
create(input: $createInput) {
success
}
}
# Is resolved serially after `a` is resolved
b: reviews {
delete(input: $deleteInput) {
success
}
}
}
Next
Home
Edit on GitHubEditForumsDiscord