A simple GraphQL service with Spring Boot and Netflix DGS

This article discusses how to implement a GraphQL server-side service with Spring WebFlux and Netflix DGS framework.

Use Case

Let's say we have to implement a service/API that will return user information

for the given input.

This service will expose two query endpoints.

  • To get a List of Users for given search criteria.

  • To get a specific User for a given ID.

Tech Stack

Java 11 or above

Spring Boot WerbFlux 2.7.x

Netflix DGS 5.5.x

Gradle

Find this article-specific code implementation here

Simple Graphql service implementation

Implementation Details

Dependencies

We need to create a template project with all the necessary dependencies.

We can use https://start.spring.io/ to bootstrap our project with the following given dependencies.

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.17'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

dependencyManagement {
    imports {
        mavenBom 'com.netflix.graphql.dgs:graphql-dgs-platform-dependencies:5.5.5'
    }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'com.netflix.graphql.dgs:graphql-dgs-webflux-starter:5.5.5'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
}

Here main thing to notice is to make sure we are choosing the correct Spring boot-compatible Netflix DGS framework version. In our case, we are using the spring boot 2.7.x version its equivalent compatible DGS version is 5.5.x.

GraphQL Schema

Now let's define our GraphQL schema as per our use case. As per our use case, we will define the below schema to support our use case.

In the spring boot application, we have to define query schema under the resources/schema/schema.graphqls folder as it's the default path where schema will be defined.


type Query {
    users(searchInput: SearchInput) : [User]
    user(id : ID): User
}

type UserResponse{
    users: [User]
}

type User {
    id: ID
    firstName: String
    lastName: String
    dateOfBirth: String
    gender: String
    address: [Address]
    phone: [Phone]
}

type Address {
    type: String
    street1: String
    street2: String
    city: String
    state: String
    zip: Int
}

type Phone {
    type: String
    number: String
    countryCode: String
}

// In input firstName & lastName is mandatory
input SearchInput {
    firstName: String!
    lastName: String!
    dateOfBirth: String
}

As per our use case, we defined two queries.

  • users -> To return a list of users for given criteria. We made this query input as a custom object SearchInput to support multiple search criteria.

  • user -> To return a specific given user based on ID.

Both these queries are defined with their respective return type objects.

The code

Now let's write our core application code. For the sake of simplicity, we will be hard coding service response.

Our application code structure will look like below.

We need to define a domain model as per our schema. So we defined User, Address, Phone, SearchInput, and UserResponse domain objects compatible with our query schema definition. All these objects will have required files and those will be accessed through Lombok library-generated getters/setters.

Next, define our DataFetcher to receive requests as per schema propagate the request to the next layer (Service layer) and get the response.

@DgsComponent
@Slf4j
public class UserDataFetcher {

    private final UserSearchService userSearchService;

    public UserDataFetcher(UserSearchService userSearchService) {
        this.userSearchService = userSearchService;
    }

    @DgsQuery
    public Mono<List<User>> users(@InputArgument SearchInput searchInput) throws JsonProcessingException {
        List<User> users = userSearchService.searchUser(searchInput);
        return Mono.just(users);
    }

    @DgsQuery
    public Mono<User> user(@InputArgument Integer id){
        User user = userSearchService.searchById(id);
        return Mono.just(user);
    }
}

@DgsComponent will make this class a spring context bean which will act as a kind of request handler.

Using the @DgsQuery framework will resolve the query name and execute the respective query-matching request method in the DataFetcher class. Here we defined our method as users which is the same as our schema so we don't need to specify explicitly the query name in @DgsQuery.

Also, we are using Spring Boot reactive stack (Webflux) for our implementation. So we can implement a Non-Blocking type of functional programming model. Each method will return the Mono<Object> type.

Now it's time for our Service layer implementation to handle this request and respond.

@Service
@Slf4j
public class UserSearchServiceImpl implements UserSearchService {

    @Override
    public List<User> searchUser(SearchInput searchInput) {
        log.info("searchUser Starts");
        return Arrays.asList(getUser());
    }

    @Override
    public User searchById(Integer id) {
        return getUser();
    }

    private User getUser() {
        User user = new User();
        user.setId(1);
        user.setFirstName("Jhon");
        user.setLastName("Victor");
        user.setGender("M");
        user.setDateOfBirth(LocalDate.now().toString());

        Address address = new Address();
        address.setType("Home");
        address.setStreet1("1234 First Street");
        address.setStreet2("Apt 9");
        address.setCity("Louisville");
        address.setState("NY");
        address.setType("Home");
        address.setZip(12345);

        user.setAddress(Arrays.asList(address));

        Phone phone = new Phone();

        phone.setType("Mobile");
        phone.setCountryCode("+1");
        phone.setNumber("1234567890");

        user.setPhone(Arrays.asList(phone));

        return user;
    }
}

To make it simple we hard-coded user object creation and returned respective required users or user objects.

Test the Application

It's time to test our application. In spring boot we can enable an inbuilt client(Graphiql) which provides an easy user-friendly option to test this implementation.

To enable it add the below property in the application.properties


spring.graphql.graphiql.enabled=true

Now when we start the application it will start by default in the 8080 port and we can access Graphiql with this URL http://localhost:8080/graphiql

This will conclude our simple GraphQL server service implementation. Happy learning.