Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing GraphQL APIs in Spring Boot: Multiple Approaches

Tech 1

Spring Boot integrates with GraphQL to provide flexible API development. Several methods exist for constructing a GraphQL schema and resolving data.

Approach One: Utilizing graphql-java-tools

Include the necessary dependencies.

<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-java-tools</artifactId>
    <version>5.6.0</version>
</dependency>
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphiql-spring-boot-starter</artifactId>
    <version>5.0.4</version>
</dependency>

Define a GraphQL Schema File (schema.graphqls).

type Query {
    productList: [Product!]
}

type Product {
    sku: Int!
    title: String!
    manufacturer: Manufacturer!
}

type Manufacturer {
    code: Int!
    companyName: String!
}

Create corresponding Java domain classes.

public class Product {
    private int sku;
    private String title;
    private int manufacturerCode;

    // Constructors, getters, and setters
}

public class Manufacturer {
    private int code;
    private String companyName;

    // Constructors, getters, and setters
}

Implement a resolver for complex fields on the Product type.

@Component
public class ProductResolver implements GraphQLResolver<Product> {
    private ManufacturerRepository manufacturerRepo;

    public ProductResolver(ManufacturerRepository manufacturerRepo) {
        this.manufacturerRepo = manufacturerRepo;
    }

    public Manufacturer manufacturer(Product product) {
        return manufacturerRepo.findByCode(product.getManufacturerCode());
    }
}

Implement the root query resolver.

@Component
public class QueryResolver implements GraphQLQueryResolver {
    private ProductRepository productRepo;

    public QueryResolver(ProductRepository productRepo) {
        this.productRepo = productRepo;
    }

    public List<Product> productList() {
        return productRepo.getAllProducts();
    }
}

If a GraphQL type's fields directly correspond to a Java class's properties, a specific resolver for that type is optional.

Field Resolution Priority When mapping a GraphQL object type field to a Java class, the library searches in this order:

  1. Method fieldName(*fieldArgs [, DataFetchingEnvironment])
  2. Method isFieldName(*fieldArgs [, DataFetchingEnvironment]) (for Boolean fields)
  3. Method getFieldName(*fieldArgs [, DataFetchingEnvironment])
  4. Method getFieldFieldName(*fieldArgs [, DataFetchingEnvironment])
  5. Field fieldName

Resolver method are checked before the Java class methods. For the manufacturer field in the Product type, the ProductResolver.manufacturer(Product) method takes precedence.

Build the executable schema.

@Configuration
public class GraphQLConfig {
    @Bean
    public GraphQLSchema schema(QueryResolver queryResolver, ProductResolver productResolver) {
        return SchemaParser.newParser()
                .file("schema.graphqls")
                .resolvers(queryResolver, productResolver)
                .build()
                .makeExecutableSchema();
    }
}

Approach Two: Manual Schema Wiring with DataFetchers

This method does not require graphql-java-tools. Define a schema file.

type Query {
  findProduct(sku: ID): Product
}

type Product {
  sku: ID
  title: String
  pageCount: Int
  manufacturer: Manufacturer
}

type Manufacturer {
  code: ID
  firstName: String
  lastName: String
}

Load the schema and configure runtime wiring with DataFetcher implementations.

@Configuration
public class GraphQLManualConfig {
    @Value("classpath:schema.graphqls")
    private Resource schemaResource;

    @Bean
    public GraphQL graphQL(GraphQLDataProvider dataProvider) throws IOException {
        TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(schemaResource.getFile());
        RuntimeWiring wiring = buildRuntimeWiring(dataProvider);
        SchemaGenerator schemaGenerator = new SchemaGenerator();
        GraphQLSchema schema = schemaGenerator.makeExecutableSchema(typeRegistry, wiring);
        return GraphQL.newGraphQL(schema).build();
    }

    private RuntimeWiring buildRuntimeWiring(GraphQLDataProvider dataProvider) {
        return RuntimeWiring.newRuntimeWiring()
                .type(TypeRuntimeWiring.newTypeWiring("Query")
                        .dataFetcher("findProduct", dataProvider.getProductDataFetcher()))
                .type(TypeRuntimeWiring.newTypeWiring("Product")
                        .dataFetcher("manufacturer", dataProvider.getManufacturerDataFetcher()))
                .build();
    }
}

Implement the DataFetcher component. The fetchers return Map objects matching the expected structure.

@Component
public class GraphQLDataProvider {
    private final List<Map<String, String>> productCatalog = Arrays.asList(
            Map.of("sku", "P001", "title", "Effective Java", "pageCount", "416", "manufacturerCode", "M001"),
            Map.of("sku", "P002", "title", "Clean Code", "pageCount", "464", "manufacturerCode", "M002")
    );

    private final List<Map<String, String>> manufacturers = Arrays.asList(
            Map.of("code", "M001", "firstName", "Joshua", "lastName", "Bloch"),
            Map.of("code", "M002", "firstName", "Robert", "lastName", "Martin")
    );

    public DataFetcher getProductDataFetcher() {
        return environment -> {
            String sku = environment.getArgument("sku");
            return productCatalog.stream()
                    .filter(product -> sku.equals(product.get("sku")))
                    .findFirst()
                    .orElse(null);
        };
    }

    public DataFetcher getManufacturerDataFetcher() {
        return environment -> {
            Map<String, String> product = environment.getSource();
            String manCode = product.get("manufacturerCode");
            return manufacturers.stream()
                    .filter(manufacturer -> manCode.equals(manufacturer.get("code")))
                    .findFirst()
                    .orElse(null);
        };
    }
}

This approach does not mandate specific Java classes. DataFetchers must return data structures compatible with the GraphQL type definitions.

Approach Three: Programmatic Schema Definition

Construct the GraphQL schema entirely in code without a .graphqls file.

@Configuration
public class ProgrammaticGraphQLConfig {
    @Bean
    public GraphQL graphQL() {
        // Define a GraphQL Object Type
        GraphQLObjectType itemType = GraphQLObjectType.newObject()
                .name("InventoryItem")
                .field(GraphQLFieldDefinition.newFieldDefinition()
                        .name("description")
                        .type(Scalars.GraphQLString))
                .build();

        // Define a DataFetcher for the type's field
        DataFetcher<String> descriptionFetcher = environment -> {
            // Business logic to fetch data
            return "Sample Item Description";
        };

        // Define the root Query type
        GraphQLObjectType queryType = GraphQLObjectType.newObject()
                .name("RootQuery")
                .field(GraphQLFieldDefinition.newFieldDefinition()
                        .name("item")
                        .type(itemType)
                        .dataFetcher(descriptionFetcher))
                .build();

        // Create the executable schema
        GraphQLSchema schema = GraphQLSchema.newSchema()
                .query(queryType)
                .build();

        return GraphQL.newGraphQL(schema).build();
    }
}

The programmatic definition above is equivalent to the following SDL:

type InventoryItem {
  description: String
}

type RootQuery {
  item: InventoryItem
}

A simple execution example:

GraphQL graphQL = // obtain GraphQL instance
ExecutionResult result = graphQL.execute("{ item { description } }");
String description = result.getData("item.description"); // "Sample Item Description"

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.