Build a GraphQL API with Guice and Spark

GraphQL is a modern query language for retrieving data from a server. There is a large amount of documentation on building APIs for a whole bunch of platforms, but unfortunately the official documentation for Java contains only one example, which involves building an application based on the Spring Framework . The example hides some implementation details, forcing the user to read the source. In this article, we will fix this and create an analogue on a combination of Google Guice and Spark . Before proceeding, I recommend that you familiarize yourself with the original tutorial , because I will not delve into the architecture of the library and the description of the Java GraphQL entities





1. Creating a Guice application

Gradle. , gradle init







:





Select type of project to generate: application
Select implementation language: Java
Select build script DSL: Kotlin
Select test framework: JUnit Jupiter
Project name: guice-spark-graphql
Source package: guice.spark.graphql
      
      



Java src/main/java/guice/spark/graphql/App.java







build.gradle.kts







dependencies {
    // This dependency is used by the application.
    implementation("com.google.guava:guava:29.0-jre")
    //Guice
    implementation("com.google.inject:guice:5.0.1") //NEW

    //Spark
    implementation("com.sparkjava:spark-core:2.9.3") //NEW
    implementation("com.sparkjava:spark-template-velocity:2.7.1") ////NEW spark template engine
    implementation("org.slf4j:slf4j-simple:1.7.21") //NEW fix Spark SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    implementation("com.fasterxml.jackson.core:jackson-databind:2.12.3") //NEW

    //graphql
    implementation("com.graphql-java:graphql-java:16.2") //NEW

    // Use JUnit Jupiter API for testing.
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.2")

    // Use JUnit Jupiter Engine for testing.
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.2")
}

      
      



Guice, GraphQL Spark Slf4j, Jackson Velocity Template. Guava , -





2. GraphQL

GraphQL , API. schema.graphqls



src/main/resources







type Query {
  bookById(id: ID): Book 
}

type Book {
  id: ID
  name: String
  pageCount: Int
  author: Author
}

type Author {
  id: ID
  firstName: String
  lastName: String
}
      
      



3. Guice

Guice DI src/main/java/guice/spark/graphql/App.java







public class App {
    @Inject
    private GraphQLService service;

    public static void main(String[] args) {
        App app = new App();

        Injector injector = Guice.createInjector(new GraphQLModule());
        injector.injectMembers(app);

        app.service.initialize();
    }
}

      
      



Guice Injector, GraphQLModule . injector.injectMembers(app)



service GraphQLService



, ,





4. GraphQLModule

Guice . src/main/java/guice/spark/graphql/GraphQLModule.java







public class GraphQLModule extends AbstractModule {
    protected void configure() {
        bind(GraphQLService.class).asEagerSingleton();
        bind(GraphQL.class).toProvider(GraphQlProvider.class).asEagerSingleton();
        bind(ObjectMapper.class).asEagerSingleton();
    }
}
      
      



configure



:





  • GraphQLService



    Spark c Spark, GraphQL





  • GraphQL







  • ObjectMapper



    JSON





5. GraphQlProvider

, GraphQL



, Provider



Guice, . GraphQlProvider







public class GraphQlProvider implements Provider<GraphQL> {

    private GraphQLDataFetchers graphQLDataFetchers;

    @Inject
    public GraphQlProvider(GraphQLDataFetchers graphQLDataFetchers) {
        this.graphQLDataFetchers = graphQLDataFetchers;
    }

    @Override
    public GraphQL get() {
        URL url = Resources.getResource("schema.graphqls");
        String sdl = null;
        try {
            sdl = Resources.toString(url, Charsets.UTF_8);
        } catch (IOException e) {
            e.printStackTrace();
        }
        GraphQLSchema graphQLSchema = buildSchema(sdl);
        return GraphQL.newGraphQL(graphQLSchema).build(); 
    }

    private GraphQLSchema buildSchema(String sdl) {
        TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
        RuntimeWiring runtimeWiring = buildWiring();
        SchemaGenerator schemaGenerator = new SchemaGenerator();
        return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
    }

    private RuntimeWiring buildWiring() {
        return RuntimeWiring.newRuntimeWiring()
                .type(newTypeWiring("Query")
                        .dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
                .type(newTypeWiring("Book")
                        .dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
                .build();
    }
}

      
      



get() , GraphQL . ,





6. GraphQLDataFetchers

DataFetcher



GraphQL Java. DataFetcher - .





src/main/java/guice/spark/graphql/GraphQLDataFetchers.java







@Singleton
public class GraphQLDataFetchers {
    private static List<Map<String, String>> books = Arrays.asList(
            ImmutableMap.of("id", "book-1",
                    "name", "Harry Potter and the Philosopher's Stone",
                    "pageCount", "223",
                    "authorId", "author-1"),
            ImmutableMap.of("id", "book-2",
                    "name", "Moby Dick",
                    "pageCount", "635",
                    "authorId", "author-2"),
            ImmutableMap.of("id", "book-3",
                    "name", "Interview with the vampire",
                    "pageCount", "371",
                    "authorId", "author-3")
    );

    private static List<Map<String, String>> authors = Arrays.asList(
            ImmutableMap.of("id", "author-1",
                    "firstName", "Joanne",
                    "lastName", "Rowling"),
            ImmutableMap.of("id", "author-2",
                    "firstName", "Herman",
                    "lastName", "Melville"),
            ImmutableMap.of("id", "author-3",
                    "firstName", "Anne",
                    "lastName", "Rice")
    );

    public DataFetcher getBookByIdDataFetcher() {
        return dataFetchingEnvironment -> {
            String bookId = dataFetchingEnvironment.getArgument("id");
            return books
                    .stream()
                    .filter(book -> book.get("id").equals(bookId))
                    .findFirst()
                    .orElse(null);
        };
    }

    public DataFetcher getAuthorDataFetcher() {
        return dataFetchingEnvironment -> {
            Map<String, String> book = dataFetchingEnvironment.getSource();
            String authorId = book.get("authorId");
            return authors
                    .stream()
                    .filter(author -> author.get("id").equals(authorId))
                    .findFirst()
                    .orElse(null);
        };
    }
}
      
      



@Singleton



Guice - . configure



GraphQLModule



, -





7. GraphQLService

API web . GraphQLService







src/main/java/guice/spark/graphql/GraphQLService.java







public class GraphQLService {
    private final GraphQL graphQL;
    private final ObjectMapper mapper;

    @Inject
    public GraphQLService(GraphQL graphQL, ObjectMapper mapper) {
        this.graphQL = graphQL;
        this.mapper = mapper;
    }

    public void initialize() {
        post("/graphql", (request, response) -> {
            GraphQLRequestBody body = mapper.readValue(request.body(), GraphQLRequestBody.class);

            String query = body.getQuery();
            if (query == null) {
                query = "";
            }

            ExecutionResult executionResult = graphQL.execute(
                    ExecutionInput.newExecutionInput()
                            .query(query)
                            .operationName(body.getOperationName())
                            .variables(body.getVariables())
                            .build()
            );

            response.type("application/json");
            return mapper.writeValueAsString(executionResult.toSpecification());
        });
        
        get("/playground", (req, res) -> new VelocityTemplateEngine().render(
                new ModelAndView(Collections.emptyMap(), "playground.html"))
        );
    }

      
      



initialize



Jetty web server 4567. :





POST http://localhost:4567/graphql ObjectMapper



GraphQLRequestBody



GraphQL . JSON .





GraphQLRequestBody:







public class GraphQLRequestBody {
    private String query;
    private String operationName;
    private Map<String, Object> variables;

    public String getQuery() {
        return query;
    }

    public void setQuery(String query) {
        this.query = query;
    }

    public String getOperationName() {
        return operationName;
    }

    public void setOperationName(String operationName) {
        this.operationName = operationName;
    }

    public Map<String, Object> getVariables() {
        return variables;
    }

    public void setVariables(Map<String, Object> variables) {
        this.variables = variables;
    }
}
      
      



GET http://localhost:4567/playground playground.html , src/main/resources



. API .





. http://localhost:4567/playground





API

. :





query {
  bookById(id: "book-1") {
    name,
    author {
      firstName
    }
  }
}

      
      



API:





{
  "data": {
    "bookById": {
      "name": "Harry Potter and the Philosopher's Stone",
      "author": {
        "firstName": "Joanne"
      }
    }
  }
}
      
      



Full source code and additional information

  • All sources can be found here





  • More information on GraphQL can be found








All Articles