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 fields 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 fields 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 fields (instead, your namespace objects are). Because of this, the mutation fields 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