fbpx

Introduction to GraphQL with Spring Boot and Kotlin

1. Introduction

In this step by step tutorial, I would like to show you how to use GraphQL with Spring Boot and Kotlin. Additionally, we will learn how to test with GraphiQL– a dedicated GUI for editing and testing GraphQL queries and mutations.

In simple words, GraphQL is a query language for APIs that provides a more flexible alternative to REST. It allows clients to define the structure of the desired data, and the same structure of the data is returned from the server, therefore preventing large amounts of data from being returned. It was developed internally by Facebook in 2012 and open-sourced in 2015.

2. Imports

As the first step, let’s add the necessary imports to our project:

implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.graphql-java:graphql-spring-boot-starter:5.0.2")
implementation("com.graphql-java:graphiql-spring-boot-starter:5.0.2")
implementation("com.graphql-java:graphql-java-tools:5.2.4")

As you can see, besides Spring Boot starters, we’ve added the GraphQL Java Tools library. It allows us to use the GraphQL schema language to build our graphql-java schema. Additionally, it parses the given GraphQL schema and allows us to BYOO (bring your own object) to fill in the implementations.

3. Create Models

As the next step, let’s implement our models. In this tutorial, we will implement a simple CRUD service for employees’ management.

Let’s start by adding a Company class with 4 simple properties:

class Company(
    val id: Long,
    val name: String,
    val address: String,
    val zipCode: String
)

As the next step, let’s implement the Employee class:

class Employee(
    val id: Long,
    val firstName: String,
    val lastName: String,
    val status: EmployeeStatus,
    val company: Company
)

enum class EmployeeStatus {
    ACTIVE, RETIRED
}

Besides the class, we’ve also added the EmployeeStatus enum, which will be used as one of the properties of Employee. Additionally, each employee will have a company property indicating the associated company.

4. Create Repositories

Nextly, let’s create repositories responsible for in-memory data management.

4.1. Implement CompanyRepository

Firstly, let’s create the CompanyRepository class and annotate it with @Component:

@Component
class CompanyRepository {

    private val companyId = AtomicLong(4)

    private val companies = mutableSetOf(
        Company(1, "Company One", "Address 1", "10001"),
        Company(2, "Company Two", "Address 2", "10002"),
        Company(3, "Company Three", "Address 3", "10003"),
        Company(4, "Company Four", "Address 4", "10004")
    )
}

CompanyRepository contains two properties: companyId– an atomic variable, which will be used to generate identifiers for companies; and companies– a set containing predefined objects.

As the next step, let’s add functions responsible for searching, creating, and deleting companies:

fun findAll(): Set = companies

fun findById(companyId: Long): Company =
    companies.find { it.id == companyId }
        ?: throw RuntimeException("Company with id [$companyId] could not be found.")

fun create(name: String, address: String, zipCode: String): Company {
    val company = Company(
        id = companyId.incrementAndGet(),
        name = name,
        address = address,
        zipCode = zipCode
    )

    companies.add(company)
    return company
}

fun delete(id: Long): Boolean =
    companies.removeIf { it.id == id }

4.2. Implement EmployeeRepository

With that being done, we can create the EmployeeRepository as follows:

@Component
class EmployeeRepository(
    private val companyRepository: CompanyRepository
) {

    val employerId = AtomicLong(3)

    val employees = mutableSetOf(
        Employee(1, "John", "Doe", EmployeeStatus.ACTIVE, companyRepository.findById(1)),
        Employee(2, "Adam", "Nowak", EmployeeStatus.ACTIVE, companyRepository.findById(2)),
        Employee(3, "Stan", "Bar", EmployeeStatus.RETIRED, companyRepository.findById(3))
    )
}

Similarly, the EmployeeRepository will have 4 simple functions:

fun findAll(): Set = employees

fun findById(employeeId: Long) =
    employees.find { it.id == employeeId }
        ?: throw RuntimeException("Employee with id [$employeeId] could not be found.")

fun create(
    firstName: String,
    lastName: String,
    status: EmployeeStatus,
    companyId: Long
): Employee {
    val company = companyRepository.findById(companyId)

    val employee = Employee(
        id = employerId.incrementAndGet(),
        firstName = firstName,
        lastName = lastName,
        status = status,
        company = company
    )

    employees.add(employee)
    return employee
}

fun delete(id: Long): Boolean =
    employees.removeIf { it.id == id }

5. Define GraphQL Schema

After we’ve implemented our models and repositories, we can start defining the schema. In simple terms, a schema is a contract between the client and the server describing the structure of the data and how the data might be operated.

For the purpose of this tutorial, let’s focus on the 4 types, we will be using:

  • Object Types– the most basic components, which represent a kind of objects we can fetch from our service (classes in our example)
  • Enums– a special kind of scalar values restricted to a particular set of values
  • Queries– every GraphQL service needs to have a Query. To put it simply, queries are used to describe how to read, or fetch values
  • Mutations– work in a similar way, as queries and are used to modify a server-side data

If you would like to learn more about the GraphQL Schema, please refer to the official documentation.

5.1. Describe Company Schema

Let’s start with the definition of company schema. Let’s create a company.graphqls file and put it under the ./src/main/resources/ directory:

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

type Query {
    companies: [Company]
    companyById(id: ID!) : Company
}

type Mutation {
    newCompany(name: String!, address: String!, zipCode: String!) : Company!
    deleteCompany(id: ID!) : Boolean
}

In this simple file, we’ve declared three types:

  • The Company type– which will be associated with the Company entity, we’ve created earlier
  • The Query type– containing two queries
  • The Mutation type– describing mutations for company creation and removal

5.2. Describe Employee Schema

As the next step, let’s create an employee.graphqls file:

type Employee {
    id: ID!
    firstName: String!
    lastName: String!
    status: EmployeeStatus!
    company: Company!
}

enum EmployeeStatus{
    ACTIVE, RETIRED
}

extend type Query {
    employees: [Employee]
    employeeById(id: ID!) : Employee
}

extend type Mutation {
    newEmployee(firstName: String!, lastName: String!, status: EmployeeStatus!, companyId: ID!) : Employee!
    deleteEmployee(id: ID!) : Boolean
}

This file is pretty similar to the previous one, but we’ve also added the Enum type here. Additionally, please notice the extend keyword- we have to place it in order to extend the Mutation and Query declared in another file.

6. Create Queries Resolvers

After we’ve defined the schema, we can start adding the code responsible for data manipulation. Let’s start by adding the CompanyQuery class:

@Component
class CompanyQuery(
    private val companyRepository: CompanyRepository
) : GraphQLQueryResolver {

    fun companies(): Set =
        companyRepository.findAll()

    fun companyById(id: Long): Company =
        companyRepository.findById(id)
}

Please notice two important things here. Firstly, the CompanyQuery class implements the GraphQLQueryResolver, which is a markup interface provided by the GraphQL dependency. Secondly, public functions need to match the schema in order to be resolved correctly.

Similarly, let’s implement the EmployeeQuery class:

@Component
class EmployeeQuery(
    private val employeeRepository: EmployeeRepository
) : GraphQLQueryResolver {

    fun employees(): Set =
        employeeRepository.findAll()

    fun employeeById(id: Long): Employee =
        employeeRepository.findById(id)
}

7. Implement Mutations Resolvers

In order for our application to be fully functional, we need to add the resolvers for our mutations. As the first step, let’s add the CompanyMutation class:

@Component
class CompanyMutation(
    private val companyRepository: CompanyRepository
) : GraphQLMutationResolver {

    fun newCompany(name: String, address: String, zipCode: String): Company =
        companyRepository.create(name, address, zipCode)

    fun deleteCompany(id: Long): Boolean =
        companyRepository.delete(id)
}

Just like in Queries resolvers, public functions need to match the structure from the defined schema. The only difference here is that we’ve implemented the GraphQLMutationResolver interface.

Finally, we can add the last class to our project which will be responsible for the employee mutations:

@Component
class EmployeeMutation(
    private val employeeRepository: EmployeeRepository
) : GraphQLMutationResolver {

    fun newEmployee(
        firstName: String,
        lastName: String,
        status: EmployeeStatus,
        companyId: Long
    ): Employee =
        employeeRepository.create(firstName, lastName, status, companyId)

    fun deleteEmployee(id: Long): Boolean =
        employeeRepository.delete(id)

}

8. Testing

Finally, we can run the application and test it. In this article, I would like to show you the tool called GraphiQL– a dedicated GUI for communicating with GraphQL servers.

In the beginning, we’ve added the GraphiQL Spring Boot Starter dependency, which allows us to run its web-based version under the /graphiql endpoint. However, you can always download it’s Electron-based wrapper here.

8.1. Test Queries

After starting the application, let’s head to the http://localhost:8080/graphiql endpoint and run the following query:

query {
  employees {
    id
    firstName
    lastName
    status
    company {
      name
    }
  }
}

As you can see, the query structure allows us to define, which fields we would like to fetch. I highly suggest you check out different combinations and see what will be the results. As a result of the above query, we should see the following output:

{
  "data": {
    "employees": [
      {
        "id": "1",
        "firstName": "John",
        "lastName": "Doe",
        "status": "ACTIVE",
        "company": {
          "name": "Company One"
        }
      }
...

Nextly, let’s try to find the employee by id:

query {
  employeeById(id: 1) {
    id
    firstName
    lastName
    status
    company {
      id
      name
    }
  }
}

# Result:

{
  "data": {
    "employeeById": {
      "id": "1",
      "firstName": "John",
      "lastName": "Doe",
      "status": "ACTIVE",
      "company": {
        "id": "1",
        "name": "Company One"
      }
    }
  }
}

Similarly, we can test company queries:

query {
  companies {
    id
    name
    address
    zipCode
  }
}

# Result:

{
  "data": {
    "companies": [
      {
        "id": "1",
        "name": "Company One",
        "address": "Address 1",
        "zipCode": "10001"
      },
...
query {
  companyById(id: 1) {
    id
    name
    address
    zipCode
  }
}

# Result:

{
  "data": {
    "companyById": {
      "id": "1",
      "name": "Company One",
      "address": "Address 1",
      "zipCode": "10001"
    }
  }
}

8.1. Test Mutations

Testing mutations is quite similar to queries. The only difference is the usage of a mutation keyword:

mutation {
  newCompany(name: "New", address: "Address new", zipCode: "10201") {
    id
    name
    address
    zipCode
  }
}

This time, a new company will be created, and the following data will be returned:

{
  "data": {
    "newCompany": {
      "id": "5",
      "name": "New",
      "address": "Address new",
      "zipCode": "10201"
    }
  }
}

Similarly, we can create a new employee:

mutation {
  newEmployee(firstName: "Piotr", lastName:"Wolak", status: ACTIVE, companyId: 2) {
    id
    firstName
    lastName
    status
    company {
      id
      name
    }
  }
}

# Result:

{
  "data": {
    "newEmployee": {
      "id": "4",
      "firstName": "Piotr",
      "lastName": "Wolak",
      "status": "ACTIVE",
      "company": {
        "id": "2",
        "name": "Company Two"
      }
    }
  }
}

Finally, let’s test delete mutations:

{
  "data": {
    "deleteCompany": true
  }
}

# Result:

{
  "data": {
    "deleteCompany": true
  }
}
mutation {
  deleteEmployee(id: 1)
}

# Result:

{
  "data": {
    "deleteEmployee": true
  }
}

9. Conclusion

And that would be all for this tutorial. I really hope that you have learned how to use GraphQL with Spring Boot and Kotlin and that testing with GraphiQL will be a piece of cake for you.

For the source code, please refer to this GitHub repository.

If you would like to ask about anything or share any feedback with me, I would be more than happy to hear from you. You can always contact me via fan page, group, or a contact form.

Share on facebook
Share on twitter
Share on linkedin

Leave a Comment

Your email address will not be published. Required fields are marked *

Subscribe to our Newsletter

Join the community and get free eBooks.

Image shows the covers of free ebooks accessible for newsletter subscribers.

You may opt out any time. Terms of Use and Privacy Policy

Find us also on...

Join the FREE weekly newsletter and get two free eBooks as well:

Image shows the covers of free ebooks accessible for newsletter subscribers.

You may opt out any time. Terms of Use and Privacy Policy

To make Codersee work, we log user data. By using our site, you agree to our Privacy Policy and Terms of Use.