Using GraphQL With Spring Boot: Interfaces and Unions

Using GraphQL With Spring Boot: Interfaces and Unions

GraphQL’s interfaces and unions provide a good way to handle multiple field types in queries

Illustration of GraphQL with Spring BootIllustration of GraphQL with Spring Boot

First and Foremost

I’ll start with the explanation of the concepts, and then we’ll look into its implementation with Spring Boot and Kotlin.

Consider the following data classes and interface.

interface Profile {
   val id: UUID,
   val name: String
}

data class User {
   override val id: UUID,
   override val name: String
}: Profile

data class Company { 
   override val id: UUID,
   override val name: String,
   val address: String
}: Profile

Now we’re going to write a GraphQL schema that models the same interfaces and classes. We’ll have a query to fetch the Profile, which will give us aUser or Company based on what the client needs in a single query.

In our GraphQL schema, when using interfaces, we’ll add the following.

interface Profile {
    id: ID
    name: String
}

type User implements Profile {
    id: ID
    name: String
}

type Company implements Profile {
    id: ID
    name: String
    address: String 
}

type Query {
  search(text: String!): [Profile]!
}

We can also use Union instead of Interface.

type User {
    id: ID
    name: String
}

type Company {
    id: ID
    name: String
    address: String 
}

union Profile = User | Company

type Query {
  search(text: String!): [Profile]!
}

Which one should we use?

It can vary based on the use case and your end goal. The following points can be used to finalize the option.

  • There isn’t always an advantage to grouping shared fields into interfaces — it depends on the use case

  • Interfaces are good for when the types have a fundamental commonality in how they should be used

  • Unions are good for documenting and forcing the client to understand how different types should be treated

Implementation With Spring Boot

1. The setup

You can skip to the third section if you know how to set up a Spring Boot project with GraphQL. Also, the code to this is available here.

Otherwise, go to https://start.spring.io/.

Select a Gradle project, and set the language as Kotlin.

You don’t have to select any dependencies for a bare-minimum implementation.

Download the ZIP file, and open it in your favorite IDE.

Jump to build.gradle.kts, and add:

implementation("com.graphql-java-kickstart:graphql-spring-boot-starter:6.0.1")
implementation("com.graphql-java-kickstart:graphql-java-tools:5.7.1")
runtimeOnly("com.graphql-java-kickstart:altair-spring-boot-starter:6.0.1")
runtimeOnly("com.graphql-java-kickstart:graphiql-spring-boot-starter:6.0.1")
runtimeOnly("com.graphql-java-kickstart:voyager-spring-boot-starter:6.0.1")
runtimeOnly("com.graphql-java-kickstart:playground-spring-boot-starter:6.0.1")

These are the GraphQL libraries you’d need to get started with your first endpoint.

2. The first endpoint

Create a file called schema.graphqls inside the resources directory.

Add the following lines:

type Query{
    hello: String
}

And then create a resolver class that can actually do something when the query is triggered by the client.

@Component
class HelloWorldResolver: GraphQLQueryResolver {
    fun hello(): String {
        return "Hello World!"
    }
}

That’s it.

Run the server using ./gradlew bootRun.

Go to your browser, and open http://localhost:8080/playground. The Playground is one of the developer tools used as a client for GraphQL APIs.

This will be the output when you trigger the query.

Query and response in the PlaygroundQuery and response in the Playground

That’s it.

3. The interface implementation

Let’s start by changing the schema. In your schema.graphqls file, add the following code.

type Query{
    hello: String
    getIVehicles: [IVehicle]!
}

interface IVehicle{
    name: String
    price: Int
}

type ICar implements IVehicle {
    name: String
    price: Int
    engineType: String
}

type IBike implements IVehicle {
    name: String
    price: Int
}

Now add some Kotlin classes and interfaces to match the schema entities.

interface IVehicle {
    val name: String
}

data class IBike(override val name: String, val price: Int): IVehicle

data class ICar(override val name: String, val price: Int, val engineType: String) : IVehicle

After this mapping, we can start creating the service that’ll fetch the IVehicle data when the getIVehicles query is triggered.

You need to create Resolver and a Service.

@Component
class VehicleResolver(private val vehicleService: VehicleService) : GraphQLQueryResolver {
    fun getIVehicles(): List<IVehicle> {
        return vehicleService.getIVehicles()
    }
}
@Service
class VehicleService {
    fun getIVehicles(): List<IVehicle> {
        return listOf(
                ICar("BMW X1", 45000, "PETROL"),
                IBike("Ducati", 90000)
        )
    }
}

That’s it.

You’ve successfully created an endpoint that can fetch both car and bike data with the same query. How cool is that?

You can run this query to fetch the data.

Query and response on PlaygroundQuery and response on Playground

Wait a minute! I forgot something.

You need to do one more thing before this actually works.

You need to add those interface types in the GraphQL’s schema parser dictionary.

The way to do that is to add this code.

@Configuration
class GraphQLConfig() {
    @Bean
    fun schemaParserDictionary(): SchemaParserDictionary {
        return SchemaParserDictionary()
                .add(IBike::class)
                .add(ICar::class)
    }
}

This needs to be done because there’s a problem in the library, and autodiscovery for these types doesn’t work unless you explicitly put it.

You can find the issue here.

That’s it.

Conclusion

I’m not adding the implementation for Union here, as it’s pretty similar to the implementation of interfaces. Don’t worry: If you’re interested, you can find the implementation here.

I’m glad you made it to the end. It means you’re interested in implementing GraphQL. Here is another article in which I share my experience of using GraphQL for six months.