Search code examples
javakotlinspring-graphql

How to implement a query with arguments for a custom type field


I want to implement pagination into custom type fields using arguments.

Using a sample schema like this:

type Book {
    title: String!
    year: Int!
}

type Author {
    name: String!
    books(page: Int, size: Int, sort: String): [Book!]!
}

type Query {
    allAuthors: [Author!]!
}

How do I implement the query for books(page: Int, size: Int, sort: String): [Book!]! with Spring GraphQL? I know that the queries for the type Query are implemented in a @Controller annotated class with @SchemaMapping annotated methods, but that does not seem to work for my data transfer AuthorDto class.

The application does not throw an exception or a warning when starting up or during runtime, but using the query just gives me a generic error telling me it is not implemented.

{
  "errors": [
    {
      "message": "The field at path '/allAuthors[0]/books' was declared as a non null type, but the code involved in retrieving data has wrongly returned a null value.  The graphql specification requires that the parent field be set to null, or if that is non nullable that it bubble up null to its parent and so on. The non-nullable type is '[Book!]' within parent type 'Author'",
      "path": [
        "allAuthors",
        0,
        "books"
      ],
      "extensions": {
        "classification": "NullValueInNonNullableField"
      }
    }
  ],
  "data": null
}

I've tried using the @SchemaMapping in the data transfer object like this:

data class AuthorDto(
   private val author: Author // author is the actual Author model class
) {

   val name: Int
       get(): author.name

    @SchemaMapping(typeName = "Author", field = "books")
    fun getBooks(
        @Argument page: Int? = null,
        @Argument size: Int? = null,
        @Argument sort: String? = null,
    ): List<Book> {
        // my implementation for creating the list with pagination
    }
}

However, Spring does not seem to pick up the implementation. Because this class uses my data model Author class in the constructor (like in the sample https://github.com/spring-projects/spring-graphql/blob/1.0.x/samples/webmvc-http/src/main/java/io/spring/sample/graphql/project/Project.java), I cannot annotate it with @Controller and doing that also wouldn't make any sense to me in the first place.

I'm using Kotlin, but I would appreciate solutions on how to accomplish that in Java.


Solution

  • To implement the query allAuthors with Spring for GraphQL you should have the following method in your @Controller -annotated class:

    @Controller
    public class MyGraphqlController {
        @QueryMapping
        List<AuthorDto> allAuthors(){
            // implementation for creating the list with pagination
        }
    }
    

    Mind a couple of things:

    1. The AuthorDto class (data class, Java record, etc) should have the same fields as the ones defined in your schema.graphqls, which seems to not be the case at the moment.

    2. In your schema.graphqls definition, I see the Author has an array of Book, which is OK, but this field has arguments

      books(page: Int, size: Int, sort: String): [Book!]!
      

    I'm not sure this is allowed inside a custom type definition in GraphQL (type Book{...}), which is not a query, mutation or subscription.


    EDIT

    Regarding the books inside the Author, I have never used @Argument inside a @SchemaMapping other than a Query or a Mutation. I would try this under your @QueryMapping:

    @SchemaMapping
    List<Book> books(Author author, @Argument Integer page, @Argument Integer size, @Argument String sort){     
        // your logic
    }