I have an Apollo GraphQL server and I have a mutation that deletes a record. This mutation receives the UUID of the resource, calls a REST (Ruby on Rails) API and that API just returns an HTTP code of success and an empty body (204 No Content) when the deletion was successful and an HTTP error code with an error message when the deletion does not work (404 or 500, typical REST delete endpoint).
When defining a GraphQL mutation I have to define the mutation return type. What should be the mutation return type?
input QueueInput {
"The queue uuid"
uuid: String!
}
deleteQueue(input: QueueInput!): ????????
I can make it work with a couple of different types of returns (Boolean, String, ...) but I want to know what is the best practice because none of the returns types I tried felt right. I think it is important that on client-side after calling the mutation I have some information about what happened if things went well (API returns 204 not content) or if some error occurred (API returns 404 or 500) and ideally have some information about the error.
A field in GraphQL must always have a type. GraphQL has the concept of null but null is not itself a type -- it simply represents the lack of value.
There is no "void" type in GraphQL. However, types are nullable by default, so regardless of a field's type, your resolver can return nothing and the field will simply resolve to null. So you can just do
type Mutation {
deleteQueue(input: QueueInput!): Boolean #or any other type
}
Or if you want a scalar that specifically represents null, you can create your own.
const { GraphQLScalarType } = require('graphql')
const Void = new GraphQLScalarType({
description: 'Void custom scalar',
name: 'Void',
parseLiteral: (ast) => null,
parseValue: (value) => null,
serialize: (value) => null,
})
and then do
type Mutation {
deleteQueue(input: QueueInput!): Void
}
That said, it's common practice to return something. For deletions, it's common to return either the deleted item or at least its ID. This helps with cache-management on the client side. It's also becoming more common to return some kind of mutation payload type to better encapsulate client errors.
There's any number of fields you could include on a "payload" type like this:
type Mutation {
deleteQueue(input: QueueInput!): DeleteQueuePayload
}
type DeleteQueuePayload {
# the id of the deleted queue
queueId: ID
# the queue itself
queue: Queue
# a status string
status: String
# or a status code
status: Int
# or even an enum
status: Status
# or just include the client error
# with an appropriate code, internationalized message, etc.
error: ClientError
# or an array of errors, if you want to support validation, for example
errors: [ClientError!]!
}
DeleteQueuePayload could even be a union of different types, enabling the client to use the __typename
to determine the result of the mutation.
What information you expose, however, depend on your specific needs, and what specific pattern you employ is boils down to opinion.
See here and here for additional discussion and examples.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With