Is it possible to not return any data when using a GraphQL mutation?

99.6k Views Asked by At

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.

5

There are 5 best solutions below

3
maxkoryukov On BEST ANSWER

(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
}
3
Locco0_0 On

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.

0
Leksat On

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.

0
user1050483 On

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

npm i graphql-scalars 
0
Antonio Petricca On

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);

    }

}