javaspring-bootgraphqlgraphql-javagraphql-java-tools

How to execute Java calls to GraphQL in a Spring Boot + GraphQL Java Tools' context?


In a Spring Boot application, we're already having a fully functional GraphQL endpoint, serving .graphqls files via GraphQL Java Tools (we included the graphql-spring-boot-starter dependency) and handling the data resolution through our base Query class implementing GraphQLQueryResolver and subsequent GraphQLResolver's.

For a business need, we have to re-create standard REST API endpoints, so I wonder why not just making calls to GraphQL (instead of having to re-implement "by hand" the data resolution once again)? And as it's in the same backend application, no need to make HTTP or servlet (ForwardRequest) calls, just call some API's in Java. The thing is I don't know how to proceed.

I read this example, but it's with basic GraphQL Java (not Tools): https://www.graphql-java.com/documentation/v9/execution/

I know this should possible because we are allowed to do this in tests: https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/master/example-graphql-tools/src/test/java/com/graphql/sample/boot/GraphQLToolsSampleApplicationTest.java

But how to do it in regular code? There is not such thing as a GraphQLTemplate.

I also tried to search through examples at:

but found nothing relevant to our need.

Found nothing more in Documentation:

What did I miss? Ideally I'm looking to inject some GraphQLSomething like this:

@RestController
@RequestMapping(path = "api")
public class CompanyController {

    @Autowired
    private GraphQLSomething graphQLSomething;

    @GetMapping("company/{id}")
    public ResponseEntity<?> societe(@PathVariable @NotNull Integer id) {
        GraphQLSomethingResult result = GraphQLSomething.query("company(id: $id) { id name andsoone }", "{ \"id\": 123456 }").execute(); // not really sure of the GraphQL syntax here, but it'll need some tests...
        return result.getDataOrElse();
    }

}

Solution

  • Finally found how to do the thing as I wanted:

    import java.util.Map;
    import java.util.Optional;
    
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Positive;
    
    import org.springframework.beans.factory.annotation.Autowired;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.ResponseEntity;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.google.common.collect.ImmutableMap;
    
    import graphql.ExecutionResult;
    import graphql.servlet.core.GraphQLQueryInvoker;
    import graphql.servlet.core.internal.GraphQLRequest;
    import graphql.servlet.input.GraphQLInvocationInputFactory;
    import graphql.servlet.input.GraphQLSingleInvocationInput;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    @Validated
    @RestController
    @RequestMapping(path = "api")
    public class CompanyController {
    
        @Autowired
        private GraphQLInvocationInputFactory invocationInputFactory;
    
        @Autowired
        private GraphQLQueryInvoker queryInvoker;
    
        @GetMapping("company/{id}")
        public ResponseEntity<?> societe(@PathVariable @NotNull Integer id) {
            String query = "query ($id: Int!) { company(id: $id) { id name andsoon } }";
            /*
             * ImmutableMap is a Guava class; you can build the map (e.g. a HashMap) on your
             * own, or simply Map.to(..) in Java 9, or even @PathVariable Map<String,
             * Object> variables as the method's parameter instead (but you'll miss the
             * validation).
             */
            Map<String, Object> variables = ImmutableMap.of("id", id);
    
            GraphQLRequest request = new GraphQLRequest(query, variables, null);
            GraphQLSingleInvocationInput invocationInput = invocationInputFactory.create(request);
            ExecutionResult result = queryInvoker.query(invocationInput);
    
            /*
             * Of course result.getData() can be null here - see also result.isDataPresent()
             * - but data/error handling's left to you
             */
            Optional<Object> company = Optional.ofNullable(result.getData().get("company"));
            return ResponseEntity.of(company);
        }
    
    }