Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

GraphQL is a powerful query language for APIs and provides a flexible and efficient way to interact with our data. When dealing with mutations, it’s typical to perform updates or additions to data on the server. However, in some scenarios, we might need to mutate without returning any data.

In GraphQL, the default behavior is to enforce non-nullability for fields in the schema, meaning that a field must always return a value and cannot be null unless explicitly marked as nullable. While this strictness contributes to the clarity and predictability of the API, there are instances where returning null might be necessary. However, it’s generally considered a best practice to avoid returning null values.

In this article, we’ll explore techniques for implementing GraphQL mutations without retrieving or returning specific information.

2. Prerequisites

For our example, we’ll need the following dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

The Spring Boot GraphQL Starter provides an excellent solution for quickly setting up a GraphQL server. By leveraging auto-configuration and adopting an annotation-based programming approach, we only need to focus on writing the essential code for our service.

We’ve included the web starter in our config because GraphQL is transport-agnostic. This utilizes Spring MVC to expose the GraphQL API over HTTP. We can access this via the default /graphql endpoint. We can also use other starters, like Spring Webflux, for different underlying implementations.

3. Using Nullable Type

Unlike some programming languages, GraphQL mandates an explicit declaration of nullability for each field in the schema. This approach enhances clarity, allowing us to convey when a field may lack value.

3.1. Writing the Schema

The Spring Boot GraphQL starter automatically locates GraphQL Schema files under the src/main/resources/graphql/** location. It builds the correct structure based on them, and wires special beans to this structure.

We’ll start by creating the schema.graphqls file, and defining the schema for our example:

type Post {
    id: ID
    title: String
    text: String
    category: String
    author: String
}

type Mutation {
    createPostReturnNullableType(title: String!, text: String!, category: String!, authorId: String!) : Int
}

We’ll have a Post entity and a mutation to create a new post. Also, for our schema to pass validation, it must have a query. So, we’ll implement a dummy query that returns a list of posts:

type Query {
    recentPosts(count: Int, offset: Int): [Post]!
}

3.2. Using Beans to Represent Types

In the GraphQL server, every complex type is associated with a Java bean. These associations are established based on the object and property names. That being said, we’ll create a POJO class for our posts:

public class Post {
    private String id;
    private String title;
    private String text;
    private String category;
    private String author;

    // getters, setters, constructor
}

Unmapped fields or methods on the Java bean are overlooked within the GraphQL schema, posing no issues.

3.3. Creating the Mutation Resolver

We must mark the handler functions with the @MutationMapping tag. These methods should be placed within regular @Controller components in our application, registering the classes as data-modifying components in our GraphQL application:

@Controller
public class PostController {

    List<Post> posts = new ArrayList<>();

    @MutationMapping
    public Integer createPost(@Argument String title, @Argument String text, @Argument String category, @Argument String author) {
        Post post = new Post();
        post.setId(UUID.randomUUID().toString());
        post.setTitle(title);
        post.setText(text);
        post.setCategory(category);
        post.setAuthor(author);
        posts.add(post);
        return null;
    }
}

We must annotate the parameters of the method with @Argument according to the properties from the schema. When declaring the schema, we determined that our mutation would return an Int type, without the exclamation mark. This allowed the return value to be null.

4. Creating Custom Scalar

In GraphQL, scalars are the atomic data types that represent the leaf nodes in a GraphQL query or schema.

4.1. Scalars and Extended Scalars

According to the GraphQL specification, all implementations must include the following scalar types: String, Boolean, Int, Float, or ID. Besides that, graphql-java-extended-scalars adds more custom-made scalars like Long, BigDecimal, or LocalDate. However, neither the original nor the extended set of scalars have a special one for the null value. So, we’ll build our scalar in this section.

4.2. Creating the Custom Scalar

To create a custom scalar, we should initialize a GraphQLScalarType singleton instance. We’ll utilize the Builder design pattern to create our scalar:

public class GraphQLVoidScalar {

    public static final GraphQLScalarType Void = GraphQLScalarType.newScalar()
      .name("Void")
      .description("A custom scalar that represents the null value")
      .coercing(new Coercing() {
          @Override
          public Object serialize(Object dataFetcherResult) {
              return null;
          }

          @Override
          public Object parseValue(Object input) {
              return null;
          }

          @Override
          public Object parseLiteral(Object input) {
              return null;
          }
      })
      .build();
}

The key components of the scalar are name, description, and coercing. Although the name and description are self-explanatory, the hard part of creating a custom scalar is graphql.schema.Coercing implementation. This class is responsible for three functions:

  • parseValue(): accepts a variable input object and transforms it into the corresponding Java runtime representation
  • parseLiteral(): receives an AST literal graphql.language.Value as input and transform it into the Java runtime representation
  • serialize(): accepts a Java object and converts it into the output shape for that scalar

Although the implementation of coercing can be quite complicated for a complex object, in our case, we’ll return null for each method.

4.3. Register the Custom Scalar

We’ll start by creating a configuration class where we register our scalar:

@Configuration
public class GraphQlConfig {
    @Bean
    public RuntimeWiringConfigurer runtimeWiringConfigurer() {
        return wiringBuilder -> wiringBuilder.scalar(GraphQLVoidScalar.Void);
    }	
}

We create a RuntimeWiringConfigurer bean where we configure the runtime wiring for our GraphQL schema. In this bean, we use the scalar() method provided by the RuntimeWiring class to register our custom type.

4.4. Integrate the Custom Scalar

The final step is to integrate the custom scalar into our GraphQL schema by referencing it using the defined name. In this case, we use the scalar in the schema by simply declaring scalar Void.

This step ensures that the GraphQL engine recognizes and utilizes our custom scalar throughout the schema. Now, we can integrate the scalar into our mutation:

scalar Void

type Mutation {
    createPostReturnCustomScalar(title: String!, text: String!, category: String!, authorId: String!) : Void
}

Also, we’ll update the mapped method signature to return our scalar:

public Void createPostReturnCustomScalar(@Argument String title, @Argument String text, @Argument String category, @Argument String author)

5. Conclusion

In this article, we explored implementing GraphQL mutations without returning specific data. We demonstrated setting up a server quickly with the Spring Boot GraphQL Starter. Furthermore, we introduced a custom Void scalar to handle null values, showcasing how to extend GraphQL’s capabilities.

As always, the complete code snippets are available over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.