GraphQL has become increasingly popular among developers since it was open-sourced in 2015. One of the reasons for its popularity is its ability to enable us to retrieve data efficiently through powerful and flexible APIs using concepts such as Interfaces, Unions, and Fragments. In GraphQL, interfaces and unions are abstract types that enable a schema field to return one of several object types. Spring for GraphQL makes implementing interfaces and unions easy and straightforward.
In this article, we’ll understand the general concept of the GraphQL interface and unions and how to implement them in Spring for GraphQL.
If you’re interested in learning more about GraphQL, be sure to check out our other posts.
GraphQL Interface
A GraphQL interface is an abstract type similar to an interface in programming languages like Java. A GraphQL field can return multiple concrete object types with the interface, enhancing APIs’ flexibility and efficiency.
For instance, imagine creating a GraphQL API that returns the courses available at a university. The university offers two types of courses: online and offline. To define this API, you can create a GraphQL interface in the following way:
interface Course {
id : ID!
name : String!
price: Float!
startDate: String!
endDate: String!
}
Online and offline course types can be defined as follows:
type OnlineCourse implements Course {
id : ID!
name : String!
price: Float!
startDate: String!
endDate: String!
}
type OfflineCourse implements Course {
id : ID!
name : String!
price: Float!
startDate: String!
endDate: String!
location: Location
}
type Location {
address : String
}
As you can see, both types contain all fields from the Course
interface, but OfflineCourse
also includes a Location
field.
Interfaces are helpful for returning objects or sets of objects of various types. By implementing an interface type, you can simplify your schema and reduce the number of queries needed to retrieve the relevant data. This can improve your application’s efficiency and overall performance.
To obtain data about courses, you can use the Query
type:
type Query {
courses: [Course!]!
}
Querying Interface
You can query the common fields of the Course
interface type just like any other GraphQL type, as follows:
query GetAllCourse {
courses {
id
name
price
startDate
endDate
}
}
Here’s the result returned by the query above:
{
"data": {
"courses": [
{
"id": "1",
"name": "Effective Java",
"price": 20.99,
"startDate": "01/01/2023",
"endDate": "01/31/2023"
},
....
{
"id": "2",
"name": "Python Basic",
"price": 16.99,
"startDate": "03/01/2023",
"endDate": "03/31/2023"
},
{
"id": "3",
"name": "Deep Learning",
"price": 36.99,
"startDate": "03/01/2023",
"endDate": "03/31/2023"
}
]
}
}
If you add __typename
to your query, you will notice that the results include both online and offline courses.
query GetAllCourse {
courses {
id
name
price
startDate
endDate
__typename
}
}
It’s worth mentioning that the __typename
field simply returns the name of an object type in string format.
Response of query with __typename
{
"data": {
"courses": [
{
"id": "1",
"name": "Effective Java",
"price": 20.99,
"startDate": "01/01/2023",
"endDate": "01/31/2023",
"__typename": "OnlineCourse"
},
....
{
"id": "2",
"name": "Python Basic",
"price": 16.99,
"startDate": "03/01/2023",
"endDate": "03/31/2023",
"__typename": "OfflineCourse"
},
{
"id": "3",
"name": "Deep Learning",
"price": 36.99,
"startDate": "03/01/2023",
"endDate": "03/31/2023",
"__typename": "OfflineCourse"
}
]
}
}
If you need to query the location field of an offline course type, you can use special syntax called fragments, as follows:
query GetAllCourseWithLocation {
courses {
id
name
price
startDate
endDate
... on OfflineCourse {
location {
address
}
}
}
}
Here is the response returned by the query above:
{
"data": {
"courses": [
{
"id": "1",
"name": "Effective Java",
"price": 20.99,
"startDate": "01/01/2023",
"endDate": "01/31/2023"
},
...
{
"id": "2",
"name": "Python Basic",
"price": 16.99,
"startDate": "03/01/2023",
"endDate": "03/31/2023",
"location": {
"address": "Pune"
}
},
{
"id": "3",
"name": "Deep Learning",
"price": 36.99,
"startDate": "03/01/2023",
"endDate": "03/31/2023",
"location": {
"address": "New Delhi"
}
}
]
}
}
The code ... on OfflineCourse
is a special syntax of GraphQL called inline fragments.
Fragment
A GraphQL fragment is a reusable code that can be shared between multiple queries and mutations.
Fragments enable you to reuse sections of GraphQL queries and break down complex queries into smaller, more manageable components.
fragment OfllineCoursePart on OfflineCourse {
location {
address
}
}
query GetAllCourseWithLocation {
courses {
id
name
price
startDate
endDate
...OfllineCoursePart
}
}
The above GraphQL query retrieves a list of all courses with their basic information (id, name, price, startDate, endDate) and their location’s address. The address is retrieved by using a fragment called “OfflineCoursePart
” that specifies the “address” field of the “location” object within each “OfflineCourse”.
Multiple Interfaces
A GraphQL type can implement multiple interfaces. For example, the following is a valid syntax.
interface CertificateType {
certificateType : String
}
interface Course {
id : ID!
name : String!
price: Float!
startDate: String!
endDate: String!
}
type OnlineCourse implements Course & CertificateType {
certificateType : String
id : ID!
name : String!
price: Float!
startDate: String!
endDate: String!
}
You can query the above GraphQL schema as:
query GetAllCourseWithLocation {
courses {
id
name
price
startDate
endDate
... on CertificateType {
certificateType
}
... on OfflineCourse {
location {
address
}
}
}
}
Interface Best Practices
The GraphQL interface enforces constraints requiring an object type to define all fields in that interface. However, creating interfaces solely to enforce field definitions is not considered a best practice.
In the below example, the Nameable interface enforces the name field in disparate types such as Cat, Dog, and Owner.
interface Nameable {
name: String
}
type Cat implements Nameable {
name: String
meowVolume: Int
}
type Dog implements Nameable {
name: String
barkVolume: Int
}
type Owner implements Nameable {
name: String
cats: [Cat]
dogs: [Dog]
}
type Query {
owners: [Owner]
}
This is not a recommended practice for interfaces because this schema does not use Nameable
as a return type for a field. This prevents clients from utilizing the polymorphic relationship in their operations.
To learn more, please refer to Recommended usage for GraphQL interfaces.
GraphQL Union
Union types are a helpful feature in GraphQL that lets you combine multiple types into one. This is especially useful when you need to return different objects of various types as a single response to a query. It’s similar to interfaces, but you can’t specify any common fields between the types.
For instance, let’s say you want to retrieve book and movie data in a single query response. You could create a union type called Media that includes both the Book
and Movie object types.
union Media = Book | Movie
type Book {
id : ID
name : String
authors : [String]
}
type Movie {
id : ID
name : String
actors : [String]
}
Then, you could define the query as:
type Query {
allMedia: Media
}
Using a union type in this scenario simplifies your schema and reduces the number of queries required to retrieve the necessary data. This can make your application more efficient and improve overall performance.
When you define a union type, you declare which object types are included in the union. This means that any query that returns a Media object could potentially include a Book
or a Movie object, depending on the data that is being queried.
query GetAllMedia {
allMedia {
__typename
... on Book {
name
publisher
author
}
... on Movie {
title
director
actors
}
}
}
The above query returns the response:
{
"data": {
"allMedia": [
{
"__typename": "Book",
"name": "Zero to One",
"publisher": "Crown Business",
"author": "Peter Thiel"
},
{
"__typename": "Book",
"name": "The Lean Startup",
"publisher": "VIKIN",
"author": "Eric Ries"
},
{
"__typename": "Movie",
"title": "The Shawshank Redemption",
"director": "Frank Darabont",
"actors": [
"Tim Robbins",
"Morgan Freeman",
"Bob Gunton"
]
},
{
"__typename": "Movie",
"title": "The Godfather",
"director": "Francis Ford Coppola",
"actors": [
"Marlon Brando",
"Al Pacino",
"James Caan"
]
}
]
}
}
Overall, union types can be an incredibly powerful tool in GraphQL, enabling you to create more flexible and efficient schemas.
Interface and Union implementation
Java polymorphism feature makes it very straightforward to implement interface and union in Spring for GraphQL.
Implementing Interface
Let’s implement the below query in Spring for GraphQL
type Query {
courses: [Course!]!
}
To implement the above query type we can define the marker interface Course as:
public interface Course {
}
And provide implementations of OnlineCourse
and OfflineCourse
as:
public record OnlineCourse(Integer id, String name, Double price, String startDate, String endDate)
implements Course {}
public record OfflineCourse(
Integer id, String name, Double price, String startDate, String endDate, Location location)
implements Course {}
And in the controller class, you can define @QueryMapping
as:
@QueryMapping()
public Collection<Course> courses() {
log.info("Fetching all courses..");
return List.of(
new OnlineCourse(1, "Effective Java", 20.99, "01/01/2023", "01/31/2023"),
new OnlineCourse(2, "Learning Python", 16.99, "03/01/2023", "03/31/2023"),
new OnlineCourse(2, "Deep Learning", 36.99, "04/01/2023", "04/30/2023"),
new OfflineCourse(
1, "Java Basic", 20.99, "01/01/2023", "01/31/2023", new Location("Mumbai")),
new OfflineCourse(
2, "Python Basic", 16.99, "03/01/2023", "03/31/2023", new Location("Pune")),
new OfflineCourse(
3, "Deep Learning", 36.99, "03/01/2023", "03/31/2023", new Location("New Delhi")));
}
You can read more about mapping GraphQL queries to implementation Spring for GraphQL : @SchemaMapping and @QueryMapping.
Implementing Union
Like Interface, you can implement union using the Java polymorphism feature.
type Query {
allMedia: [Media]
}
union Media = Book | Movie
To implement the above query type you can define the marker interface Media
as:
public interface Media {}
public record Book(String name, String publisher, String author) implements Media {}
public record Movie(String title, String director, List<String> actors) implements Media {}
And in the controller class, you can define @QueryMapping
as:
@QueryMapping()
public Collection<Media> allMedia() {
log.info("Fetching all media..");
return List.of(
new Book("Zero to One", "Crown Business", "Peter Thiel"),
new Book("The Lean Startup", "VIKIN", "Eric Ries"),
new Movie(
"The Shawshank Redemption",
"Frank Darabont",
List.of("Tim Robbins", "Morgan Freeman", "Bob Gunton")),
new Movie(
"The Godfather",
"Francis Ford Coppola",
List.of("Marlon Brando", "Al Pacino", "James Caan")));
}
That’s it. It’s as simple as it gets.
Code example
The working code example of this article is listed on GitHub. To run the example, clone the repository and import graphql-interface as a project in your favorite IDE as a Gradle
project.
Summary
In this article, we saw an overview of GraphQL interfaces, unions, and fragments. With interfaces, a field in GraphQL can return various object types, while unions can merge multiple types into a single one. Fragments are reusable bits of code that can be shared across distinct queries and mutations. We also saw examples of how to use interfaces and unions in Spring for GraphQL.