92

I have several GraphQL queries and mutations, now I'm trying to implement a delete mutation without returning any data:

    type Mutation{
        addElement(element: ElementData): ID
        removeElement(id: ID): ¿?
    }

However, it seems to be required to have a return value for the delete operation. Is there a way to perform an "empty" response in GraphQL? I would like to avoid things like returning a boolean or status flag if possible.

I'm not sure on what are the best practices for GraphQL delete operations.

Antonio Petricca
  • 8,891
  • 5
  • 36
  • 74
angrykoala
  • 3,774
  • 6
  • 30
  • 55
  • You could go out of your way to introduce a `Void` type as shown below, but **the return value is how the client tells whether an operation succeeded.** It's possible for the request to succeed (e.g., status 200) and yet one or more mutations fail (e.g., because of a database error). – Arel Jun 13 '23 at 17:19

5 Answers5

87

According to this Github issue you cannot return nothing.

You can define a return type which is nullable e.g.

type Mutation {
  addElement(element: ElementData): ID
  removeElement(id: ID): Boolean
}

But I suggest you return the id of the deleted element, because if you want to work with a cached store you have to update the store when the delete mutation has ran successfully.

Max
  • 1,054
  • 1
  • 12
  • 20
Locco0_0
  • 3,420
  • 5
  • 30
  • 42
  • 29
    In case of deletion you are better off returning the product ID, as suggested (since it's graphql, perhaps even the whole product). However, some operations truly require no data. For those cases one could define ```type Void``` and then do ```someOperation(input: InputObject!): Void``` to indicate the intent clearly. – Avius Oct 15 '18 at 15:20
  • 1
    An example of a mutation that needs to return value is a logout, which would just destroy the session. – Sandy Apr 02 '22 at 09:28
  • @Sandy, even with a logout mutation, a client may still want to know whether the operation succeeded (in which case a return value is necessary). A mutation that truly needs no return value would be one in which the API developer wants *no* client to know whether it succeeded or not. I'm sure there are examples, but they are likely uncommon. – Arel Jun 13 '23 at 17:29
35

(A) Solution with graphql-scalars

The original answer is below.

Here is another one solution with graphql-scalars library:

  1. install npm install graphql-scalars and then
  2. import their Void type: https://www.graphql-scalars.dev/docs/scalars/void

(B) Solution with a custom scalar

Note: design with void-result from mutations goes against "GQL best practices"

This example was written for NodeJS Apollo Framework, but it is pretty easy to convert the implementation for your language/framework

I'm pretty sure: there is an NPM-package named graphql-void but if you don't want to add another one dependency just copy this code.

1. define Void-scalar in your schema

# file: ./schema.gql

scalar Void

2. implement resolver

// file ./scalar-void.js

import { GraphQLScalarType } from 'graphql'

const Void = new GraphQLScalarType({
    name: 'Void',

    description: 'Represents NULL values',

    serialize() {
        return null
    },

    parseValue() {
        return null
    },

    parseLiteral() {
        return null
    }
})
export Void

3. add the resolver to ApolloServer

Add the Void resolver to the options of your instance of Apollo Server:

# file: ./server.js

import { ApolloServer } from 'apollo-server-express'
import { Void } from './scalar-void'

const server = new ApolloServer({
    typeDefs,  // use your schema
    resolvers: {
        Void: Void,
        // ... your resolvers
    },
    
})

4. use Void for your mutations in the schema

Finally, use the new scalar in your schema:

# file: ./schema.gql

type Mutation{
    addElement(element: ElementData): ID
    removeElement(id: ID): Void
}
maxkoryukov
  • 4,205
  • 5
  • 33
  • 54
  • 8
    What part of the linked "GQL best practices" do you suggest is saying that a void-returning mutation is bad practise? I was unable to find any such recommendation. – MEMark Sep 28 '20 at 09:04
  • 2
    @MEMark https://graphql-rules.com/rules/mutation-payload : _"Every mutation should have a unique payload type"_ . and I'm going to update the link in the answer – maxkoryukov Jul 22 '21 at 18:21
9

If you use TypeScript and graphql-codegen:

  • In the GraphQL schema:

    scalar Void
    
    type Mutation {
      removeElement(id: ID): Void
    }
    
  • In the codegen config for resolvers:

    config:
      scalars:
        Void: "void"
    

With this config TypeScript will ensure that nothing is returned from the removeElement mutation resolver. And the returning value for the mutation will always be null on the GraphQL side.

Leksat
  • 2,923
  • 1
  • 27
  • 26
1

Check out graphql-scalars Void. This is standard boilerplate for all of my GraphQL projects.

npm i graphql-scalars 
user1050483
  • 151
  • 2
  • 4
0

Here my solution...

mutation.graphql

type Mutation {

    invokeNullMutation: Void

}

GraphQLScalar.java

@Internal
public class GraphQLVoidScalar {

    static final Coercing<Void, Void> COERCING = new Coercing<>() {

        @Override
        public Void serialize(Object dataFetcherResult, GraphQLContext graphQLContext, Locale locale) {
            return null;
        }

        @Override
        public Void parseValue(Object input, GraphQLContext graphQLContext, Locale locale) {
            return null;
        }

        @Override
        public Void parseLiteral(Value<?> input, CoercedVariables variables, GraphQLContext graphQLContext, Locale locale) {
            return null;
        }

        @Override
        public Value<?> valueToLiteral(Object input, GraphQLContext graphQLContext, Locale locale) {
            return Coercing.super.valueToLiteral(input, graphQLContext, locale);
        }
    };

    public static final GraphQLScalarType INSTANCE = GraphQLScalarType
        .newScalar()
        .name("Void")
        .description("Void scalar wrapper")
        .coercing(COERCING)
        .build();

}

GraphQLConfiguration.java

public class GraphQLConfiguration {

    @Bean
    public RuntimeWiringConfigurer runtimeWiringConfigurer() {

        return wiringBuilder -> wiringBuilder
            .scalar(GraphQLCustomScalars.VOID);

    }

}
Antonio Petricca
  • 8,891
  • 5
  • 36
  • 74