All docs and tutorials usually show simple examples of mutations that look like this:
extend type Mutation {
edit(postId: String): String
}
But this way the edit method has to be unique across all entities, which to me seems like not a very robust way to write things. I would like to describe mutation similar to how we describe Queries, something like this:
type PostMutation {
edit(postId: String): String
}
extend type Mutation {
post: PostMutation
}
This seems to be a valid schema (it compiles and I can see it reflected in the generated graph-i-ql docs). But I can't find a way to make resolvers work with this schema.
Is this a supported case for GraphQL?
It's possible but generally not a good idea because:
It breaks the spec. From section 6.3.1:
Because the resolution of fields other than top‐level mutation fields must always be side effect‐free and idempotent, the execution order must not affect the result, and hence the server has the freedom to execute the field entries in whatever order it deems optimal.
In other words, only fields on the mutation root type should have side effects like CRUD operations.
Having the mutations at the root makes sense conceptually. Whatever action you're doing (liking a post, verifying an email, submitting an order, etc.) doesn't rely on GraphQL having to resolve additional fields before the action is taken. This is unlike when you're actually querying data. For example, to get comments on a post, we may have to resolve a user field, then a posts field and then finally the comments field for each post. At each "level", the field's contents are dependent on the value the parent field resolved to. This normally is not the case with mutations.
Under the hood, mutations are resolved sequentially. This is contrary to normal field resolution which happens in parallel. That means, for example, the firstName and lastName of a User type are resolved at the same time. However, if your operation type is mutation, the root fields will all be resolved one at a time. So in a query like this:
mutation SomeOperationName {
createUser
editUser
deleteUser
}
Each mutation will happen one at a time, in the order that they appear in the document. However, this only works for the root and only when the operation is a mutation, so these three fields will resolve in parallel:
mutation SomeOperationName {
user {
create
edit
delete
}
}
If you still want to do it, despite the above, this is how you do it when using makeExecutableSchema, which is what Apollo uses under the hood:
const resolvers = {
Mutation: {
post: () => ({}), // return an empty object,
},
PostMutation: {
edit: () => editPost(),
},
// Other types here
}
Your schema defined PostMutation as an object type, so GraphQL is expecting that field to return an object. If you omit the resolver for post, it will return null, which means none of the resolvers for the returning type (PostMutation) will be fired. That also means, we can also write:
mutation {
post
}
which does nothing but is still a valid query. Which is yet another reason to avoid this sort of schema structure.
Absolutely disagree with Daniel!
This is an amazing approach which helps to frontenders fastly understand what operations have one or another resource/model. And do not list loooong lists of mutations.
Calling multiple mutations in one request is common antipattern. For such cases better to create one complex mutation.
But even if you need to do such operation with several mutations you may use aliases:
await graphql({
schema,
source: `
mutation {
op1: article { like(id: 1) }
op2: article { like(id: 2) }
op3: article { unlike(id: 3) }
op4: article { like(id: 4) }
}
`,
});
expect(serialResults).toEqual([
'like 1 executed with timeout 100ms',
'like 2 executed with timeout 100ms',
'unlike 3 executed with timeout 5ms',
'like 4 executed with timeout 100ms',
]);
See the following test case: https://github.com/nodkz/conf-talks/blob/master/articles/graphql/schema-design/tests/mutations-test.js
Methods like/unlike are async with timeouts and works sequentially
Related
Problem definition
In the project I'm currently working on we're using React with Apollo Client.
On all our mutations, we have the following fields in our response:
ok
errors {
field
messages
}
The back-end extends all mutations with these fields, hence it would be nice to have a good, short way to include these fields on all mutations in the front-end, as well as be able to change this "fragment" in the future.
Hence I'm interested in shortening these same 4 lines into 1 across all my mutations.
What I've tried so far:
I've tried looking into Apollo fragments, however they seem to require a type that the fields are on or "relating to", eg.
fragment NameParts on Person {
firstName
lastName
}
Here, the frament NameParts is created using Person. However, I'm interested in extending ALL mutations.
It would be nice if I could make a generic fragment like so:
fragment OkAndErrors {
ok
errors {
field
messages
}
}
This does not seem to be possible.
I've also tried making a string, and importing it into my mutation like so:
export const OK_AND_ERRORS: string = `
ok
errors {
field
messages
}
`;
import { gql } from "apollo-boost";
import { OK_AND_ERRORS } from "./OK_AND_ERRORS";
export const CREATE_API = gql`
mutation CreateApi($newApi: ApiCreateGenericType!) {
createDrugapi(newDrugapi: $newDrugapi) {
${OK_AND_ERRORS}
(...rest of mutation is omitted for brevity)
}
}
`;
Again, it did not work.
I'm not sure if I can use gql function in a smart way with strings or JSON?
There's also inline fragments but I'm in doubt if it can be used for what I need, and the documentation of inline-fragments in Apollo is scarce.
In essence: Is there a smart way to extend Apollo mutations? Does generic fragments exist?
First of all, fragments aren't limited to Apollo but are part of just regular GraphQL queries. The GraphQL site itself actually has a nice explanation of them: https://graphql.org/learn/queries/#fragments
Essentially we can put fragments onto any query to extract data dependencies, but they're also useful for matching types using their on X type conditions.
In your case, you're saying that each mutation returns a kind of result type that has a common errors field. This tells me that you may have a MutationError kind of type already. However these MutationResult types that all have an errors fields should all implement an interface, if they don't already.
Interfaces are a great tool in the schema language to define explicitly that a type implementing it must always include a certain set of fields, in this case the errors fields. This means that we'd write our results like so:
interface MutationResult {
errors: [MutationError!]
}
type ExampleMutationResult implements MutationResult {
ok: Boolean
errors: [MutationError!]
}
type UserMutationResult implements MutationResult {
user: User
errors: [MutationError!]
}
As you can see above, the MutationResult interface is now implemented by several results, which allows me to write a reusable fragment that can be applied to any type that implements it, e.g.
fragment MutationResultErrors on MutationResult {
errors {
field
messages
}
}
Which I can then start using for all mutation queries that I'm defining. This is more predictable and expected in GraphQL rather than doing some client-side document transformations, string interpolations in a query, or something similar, as it'll be baked into your schema.
Side note: What I'd also say is, I've found that it's commonly seen that people have started to split their mutations into "errors" and "results" and made some kind of union or interface to differentiate between the two. But often they then implement generic errors with messages. It's important to say that errors that don't carry any relational data are actually already baked into GraphQL: https://spec.graphql.org/June2018/#sec-Errors
I have a mutation:
const createSomethingMutation = gql`
mutation($data: SomethingCreateInput!) {
createSomething(data: $data) {
something {
id
name
}
}
}
`;
How do I create many Somethings in one request? Do I need to create a new Mutation on my GraphQL server like this:
mutation {
addManySomethings(data: [SomethingCreateInput]): [Something]
}
Or is there a way to use the one existing createSomethingMutation from Apollo Client multiple times with different arguments in one request?
You can in fact do this using aliases, and separate variables for each alias:
const createSomethingMutation = gql`
mutation($dataA: SomethingCreateInput!) {
createA: createSomething(data: $dataA) {
something {
id
name
}
}
createB: createSomething(data: $dataB) {
something {
id
name
}
}
}
`;
You can see more examples of aliases in the spec.
Then you just need to provide a variables object with two properties -- dataA and dataB. Things can get pretty messy if you need the number of mutations to be dynamic, though. Generally, in cases like this it's probably easier (and more efficient) to just expose a single mutation to handle creating/updating one or more instances of a model.
If you're trying to reduce the number of network requests from the client to server, you could also look into query batching.
It's not possible so easily.
Because the mutation has one consistent name and graphql will not allow to have the same operation multiple times in one query. So for this to work Apollo would have to map the mutations into aliases and then even map the variables data into some unknown iterable form, which i highly doubt it does.
All docs and tutorials usually show simple examples of mutations that look like this:
extend type Mutation {
edit(postId: String): String
}
But this way the edit method has to be unique across all entities, which to me seems like not a very robust way to write things. I would like to describe mutation similar to how we describe Queries, something like this:
type PostMutation {
edit(postId: String): String
}
extend type Mutation {
post: PostMutation
}
This seems to be a valid schema (it compiles and I can see it reflected in the generated graph-i-ql docs). But I can't find a way to make resolvers work with this schema.
Is this a supported case for GraphQL?
It's possible but generally not a good idea because:
It breaks the spec. From section 6.3.1:
Because the resolution of fields other than top‐level mutation fields must always be side effect‐free and idempotent, the execution order must not affect the result, and hence the server has the freedom to execute the field entries in whatever order it deems optimal.
In other words, only fields on the mutation root type should have side effects like CRUD operations.
Having the mutations at the root makes sense conceptually. Whatever action you're doing (liking a post, verifying an email, submitting an order, etc.) doesn't rely on GraphQL having to resolve additional fields before the action is taken. This is unlike when you're actually querying data. For example, to get comments on a post, we may have to resolve a user field, then a posts field and then finally the comments field for each post. At each "level", the field's contents are dependent on the value the parent field resolved to. This normally is not the case with mutations.
Under the hood, mutations are resolved sequentially. This is contrary to normal field resolution which happens in parallel. That means, for example, the firstName and lastName of a User type are resolved at the same time. However, if your operation type is mutation, the root fields will all be resolved one at a time. So in a query like this:
mutation SomeOperationName {
createUser
editUser
deleteUser
}
Each mutation will happen one at a time, in the order that they appear in the document. However, this only works for the root and only when the operation is a mutation, so these three fields will resolve in parallel:
mutation SomeOperationName {
user {
create
edit
delete
}
}
If you still want to do it, despite the above, this is how you do it when using makeExecutableSchema, which is what Apollo uses under the hood:
const resolvers = {
Mutation: {
post: () => ({}), // return an empty object,
},
PostMutation: {
edit: () => editPost(),
},
// Other types here
}
Your schema defined PostMutation as an object type, so GraphQL is expecting that field to return an object. If you omit the resolver for post, it will return null, which means none of the resolvers for the returning type (PostMutation) will be fired. That also means, we can also write:
mutation {
post
}
which does nothing but is still a valid query. Which is yet another reason to avoid this sort of schema structure.
Absolutely disagree with Daniel!
This is an amazing approach which helps to frontenders fastly understand what operations have one or another resource/model. And do not list loooong lists of mutations.
Calling multiple mutations in one request is common antipattern. For such cases better to create one complex mutation.
But even if you need to do such operation with several mutations you may use aliases:
await graphql({
schema,
source: `
mutation {
op1: article { like(id: 1) }
op2: article { like(id: 2) }
op3: article { unlike(id: 3) }
op4: article { like(id: 4) }
}
`,
});
expect(serialResults).toEqual([
'like 1 executed with timeout 100ms',
'like 2 executed with timeout 100ms',
'unlike 3 executed with timeout 5ms',
'like 4 executed with timeout 100ms',
]);
See the following test case: https://github.com/nodkz/conf-talks/blob/master/articles/graphql/schema-design/tests/mutations-test.js
Methods like/unlike are async with timeouts and works sequentially
All docs and tutorials usually show simple examples of mutations that look like this:
extend type Mutation {
edit(postId: String): String
}
But this way the edit method has to be unique across all entities, which to me seems like not a very robust way to write things. I would like to describe mutation similar to how we describe Queries, something like this:
type PostMutation {
edit(postId: String): String
}
extend type Mutation {
post: PostMutation
}
This seems to be a valid schema (it compiles and I can see it reflected in the generated graph-i-ql docs). But I can't find a way to make resolvers work with this schema.
Is this a supported case for GraphQL?
It's possible but generally not a good idea because:
It breaks the spec. From section 6.3.1:
Because the resolution of fields other than top‐level mutation fields must always be side effect‐free and idempotent, the execution order must not affect the result, and hence the server has the freedom to execute the field entries in whatever order it deems optimal.
In other words, only fields on the mutation root type should have side effects like CRUD operations.
Having the mutations at the root makes sense conceptually. Whatever action you're doing (liking a post, verifying an email, submitting an order, etc.) doesn't rely on GraphQL having to resolve additional fields before the action is taken. This is unlike when you're actually querying data. For example, to get comments on a post, we may have to resolve a user field, then a posts field and then finally the comments field for each post. At each "level", the field's contents are dependent on the value the parent field resolved to. This normally is not the case with mutations.
Under the hood, mutations are resolved sequentially. This is contrary to normal field resolution which happens in parallel. That means, for example, the firstName and lastName of a User type are resolved at the same time. However, if your operation type is mutation, the root fields will all be resolved one at a time. So in a query like this:
mutation SomeOperationName {
createUser
editUser
deleteUser
}
Each mutation will happen one at a time, in the order that they appear in the document. However, this only works for the root and only when the operation is a mutation, so these three fields will resolve in parallel:
mutation SomeOperationName {
user {
create
edit
delete
}
}
If you still want to do it, despite the above, this is how you do it when using makeExecutableSchema, which is what Apollo uses under the hood:
const resolvers = {
Mutation: {
post: () => ({}), // return an empty object,
},
PostMutation: {
edit: () => editPost(),
},
// Other types here
}
Your schema defined PostMutation as an object type, so GraphQL is expecting that field to return an object. If you omit the resolver for post, it will return null, which means none of the resolvers for the returning type (PostMutation) will be fired. That also means, we can also write:
mutation {
post
}
which does nothing but is still a valid query. Which is yet another reason to avoid this sort of schema structure.
Absolutely disagree with Daniel!
This is an amazing approach which helps to frontenders fastly understand what operations have one or another resource/model. And do not list loooong lists of mutations.
Calling multiple mutations in one request is common antipattern. For such cases better to create one complex mutation.
But even if you need to do such operation with several mutations you may use aliases:
await graphql({
schema,
source: `
mutation {
op1: article { like(id: 1) }
op2: article { like(id: 2) }
op3: article { unlike(id: 3) }
op4: article { like(id: 4) }
}
`,
});
expect(serialResults).toEqual([
'like 1 executed with timeout 100ms',
'like 2 executed with timeout 100ms',
'unlike 3 executed with timeout 5ms',
'like 4 executed with timeout 100ms',
]);
See the following test case: https://github.com/nodkz/conf-talks/blob/master/articles/graphql/schema-design/tests/mutations-test.js
Methods like/unlike are async with timeouts and works sequentially
I have an application that has multiple profiles for a given user. The user profile can be toggled from the application's header, so that toggle can happen from any page/route in the app.
Because toggling can happen anywhere, I'm finding that I need to retrieve the fragments for every possible page, so that after the mutation succeeds, the page is updated, regardless of which route is active. This is neither performant, nor scalable. My current mutation query looks something like this:
mutation UserProfile_Mutation($input: !UserProfileInput) {
updateProfile(input: $input) {
profile {
...Page1_profile
...Page2_profile
...etc
}
}
}
I could create a different mutation query for each page, and then have the mutation function look up the query based on the route... That seems like it'd work but feels verbose and not particularly elegant.
Is there some cleaner way I can dynamically specify which fragments I want?
You could run or skip the fragments conditionally. Take a look at this:
Relay Conditional Fields
So, you could pass extra arguments to your mutation (maybe a new type of object?), and then, use this values to run or not the fragments. For example:
mutation UserProfile_Mutation($input: !UserProfileInput, $extraArg: Boolean!) {
updateProfile(input: $input) {
profile {
...Page1_profile #include(if: $extraArg)
...Page2_profile #include(if: $extraArg) // or any other arg
...etc
}
}
}
Hope it helps! :)