GraphQL with Micronaut application

Run your aggregation queries at a speed of 14x without spending $$$
Reading Time: 4 minutes

In this article, we will implement a Micronaut application written in Java that uses GraphQL to expose the data. Let’s have a quick overview of the GraphQL and Micronaut frameworks before we move on to the implementation.

GraphQL

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

This technology was developed by Facebook for querying the existing data in a declarative way. The client can specify the precise data it requires, traverse child resources with a single request, and execute multiple queries with only a single request.

Micronaut

A modern, JVM-based, full-stack framework for building modular, easily testable microservice and serverless applications.”

Micronaut offers several capabilities comparable to already-existing frameworks like Spring, but it also has some unique characteristics that make it stand out. It also provides a number of options to develop applications with support for Java, Groovy, and Kotlin.

Now, that we have some idea about the two frameworks let’s stitch them together in the implementation.

Implementing GraphQL with Micronaut

Generate Micronaut application

We have the option of generating the Micronaut application using the Micronaut Command Line Interface or with the Micronaut Launch. Here, we are using the launch option to generate the application by choosing the below options

  • Application Type as Micronaut Application
  • Java version 11
  • Name of the application: “micronaut-with-graphql”
  • Base package name: “com.knoldus.micronaut”
  • Click on the Features button and choose graphQL dependency
  • Click on Generate Project

Describe the schema

By default GraphQL endpoint /graphql is enabled so you don’t need to add any extra configuration. Create the file schema.graphqls in resources directory:

src/main/resources

type Query {

    employeeById(id: ID): Employee

}

type Employee {

    id: ID

    name: String

    empId: Int

    address: Address

}

type Address {

    id: ID

    addressLine1: String

    addressLine2: String

}

Implement Model classes

Create Employee and Address classes that will mimic the data we want to expose:

src/main/com/knoldus/micronaut/dto/Employee.java

package com.knoldus.micronaut.dto;

import io.micronaut.core.annotation.Introspected;

@Introspected
public class Employee {

    private final String id;
    private final String name;
    private final int empId;
    private final Address address;

    public Employee(String id, String name, int empId, Address address) {
        this.id = id;
        this.name = name;
        this.empId = empId;
        this.address = address;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getEmpId() {
        return empId;
    }

    public Address getAddress() {
        return address;
    }
}

src/main/com/knoldus/micronaut/dto/Address.java

package com.knoldus.micronaut.dto;

import io.micronaut.core.annotation.Introspected;

@Introspected
public class Address {

private final String id;
private final String addressLine1;
private final String addressLine2;

public Address(String id, String addressLine1, String addressLine2) {
this.id = id;
this.addressLine1 = addressLine1;
this.addressLine2 = addressLine2;
}

public String getId() {
return id;
}

public String getAddressLine1() {
return addressLine1;
}

public String getAddressLine2() {
return addressLine2;
}
}

Data Repository

Create a database repository that will pull the data from the database and make it available for the client. To keep this example simple, instead of retrieving the information from a database we will keep it in memory and just return it from there.

src/main/com/knoldus/micronaut/repository/DbRepository.java

package com.knoldus.micronaut.repository;

import com.knoldus.micronaut.dto.Address;
import com.knoldus.micronaut.dto.Employee;
import jakarta.inject.Singleton;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Singleton
public class DbRepository {

    private static final List<Employee> EMPLOYEES = Arrays.asList(
            new Employee("employee-1", "John Doe", 223, new Address("addr-1", "SmallVille", "DC")),
            new Employee("employee-2", "Moby Simons", 635, new Address("addr-2", "Gotham", "DC")),
            new Employee("employee-3", "Jennifer Stone", 371, new Address("addr-3", "Khandaq", "DC"))
    );

    public List<Employee> findAllBooks() {
        return EMPLOYEES;
    }

    public List<Address> findAllAuthors() {
        return EMPLOYEES.stream()
                .map(Employee::getAuthor)
                .collect(Collectors.toList());
    }
}

Data Fetchers

With a Data Fetcher, we bind the GraphQL schema, and our domain model and execute the appropriate queries in our datastore to retrieve the requested data.

src/main/com/knoldus/micronaut/config/GraphQLDataFetchers.java

package com.knoldus.micronaut.config;

import com.knoldus.micronaut.dto.Address;
import com.knoldus.micronaut.dto.Employee;
import com.knoldus.micronaut.repository.DbRepository;
import graphql.schema.DataFetcher;
import jakarta.inject.Singleton;

@Singleton
public class GraphQLDataFetchers {

    private final DbRepository dbRepository;

    public GraphQLDataFetchers(DbRepository dbRepository) {
        this.dbRepository = dbRepository;
    }

    public DataFetcher<Employee> getEmployeeByIdDataFetcher() {
        return dataFetchingEnvironment -> {
            String employeeId = dataFetchingEnvironment.getArgument("id");
            return dbRepository.findAllBooks()
                    .stream()
                    .filter(employee -> employee.getId().equals(employeeId))
                    .findFirst()
                    .orElse(null);
        };
    }

    public DataFetcher<Address> getAdressDataFetcher() {
        return dataFetchingEnvironment -> {
            Employee employee = dataFetchingEnvironment.getSource();
            Address addressBook = employee.getAuthor();
            return dbRepository.findAllAuthors()
                    .stream()
                    .filter(address -> address.getId().equals(addressBook.getId()))
                    .findFirst()
                    .orElse(null);
        };
    }

}
  • Constructor injection for the DbRepository bean
  • Return a GraphQL dataFetchingEnvironment with the information about the query
  • Get the id parameter from the query
  • Access the repository to find the book. Remember that this should be backed by a real datastore
  • Get the Employee related to a specific address
  • Get the Address
  • Access the repository to find the Address.

Factory

Create the following factory that will bind the GraphQL schema to the code and types.

src/main/com/knoldus/micronaut/config/GraphQLFactory.java

package com.knoldus.micronaut.config;

import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
import io.micronaut.core.io.ResourceResolver;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Optional;

import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring;

@Factory
public class GraphQLFactory {

    private static final Logger LOG = LoggerFactory.getLogger(GraphQLFactory.class);

    @Bean
    @Singleton
    public GraphQL graphQL(ResourceResolver resourceResolver, GraphQLDataFetchers graphQLDataFetchers) {
        SchemaParser schemaParser = new SchemaParser();

        TypeDefinitionRegistry typeRegistry = new TypeDefinitionRegistry();
        Optional<InputStream> graphqlSchema = resourceResolver.getResourceAsStream("classpath:schema.graphqls");

        if (graphqlSchema.isPresent()) {
            typeRegistry.merge(schemaParser.parse(new BufferedReader(new InputStreamReader(graphqlSchema.get()))));

            RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
                    .type(newTypeWiring("Query")
                            .dataFetcher("employeeById", graphQLDataFetchers.getEmployeeByIdDataFetcher()))
                    .type(newTypeWiring("Employee")
                            .dataFetcher("address", graphQLDataFetchers.getAdressDataFetcher()))
                    .build();

            SchemaGenerator schemaGenerator = new SchemaGenerator();
            GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);

            return GraphQL.newGraphQL(graphQLSchema).build();

        } else {
            LOG.debug("No GraphQL services found, returning empty schema");
            return new GraphQL.Builder(GraphQLSchema.newSchema().build()).build();
        }
    }

}
  • Annotate the class with @Factory so the Micronaut framework knows that this class will create beans
  • Create a new SchemaParser
  • Get the previously created schema.graphqls file from the classpath
  • Parse the schema
  • Create the runtime wiring
  • Bind a data fetcher for the employeeById query
  • Bind a data fetcher to retrieve the author related to a book
  • Create the executable schema
  • Return the GraphQL bean

Run the application

To run the application, use the ./mvnw mn:run command, which starts the application on port 8080.

Run the following curl command

curl -X POST 'http://localhost:8080/graphql' -H 'content-type: application/json' --data-binary '{"query":"{ employeeById(id:\"employee-1\") { name, empId, address { addressLine1, addressLine2} } }"}'

You will get the below result

Conclusion

This is how we can implement GraphQL in a micronaut application. As we can see it is pretty simple to fetch data in a single query call and this can be utilized in the event-based CQRS architecture to have a simple and clean query service.

Reference

https://guides.micronaut.io/latest/micronaut-graphql-gradle-java.html