DEV Community

Johnny Boy
Johnny Boy

Posted on

GraphQL + Kotlin & Spring Boot - Part 2 - CRUD Operations

Summary

Hey people! Today we are going to continue our Kotlin with GraphQL tutorials.
We are going to take as a code base https://github.com/Grekz/example-kotlin-crud that we did in our previous post here.

Test that the base code works

First let us make sure that you have everything ready. Input these command:

$ docker run --rm --name test-mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=kotlin_crud_db -d mysql:latest
$ gradle bootRun

If you have trouble running it, make sure to follow the previous post.

Update the TaskService.kt file

First of all we need to do some changes in our TaskService, to reuse most of the code in our GraphQL Resolvers.

// .../kotlin/com/grekz/crudKotlin/service/TaskService.kt
package com.grekz.crudKotlin.service

import com.grekz.crudKotlin.model.Task
import com.grekz.crudKotlin.repository.TaskRepository
import org.springframework.stereotype.Service
import java.util.*

@Service
class TaskService(private val taskRepository: TaskRepository) {

  fun getTasks(): List<Task> =
    taskRepository.findAll()

  fun addTask(task: Task): Task = taskRepository.save(task)

  fun getTaskById(taskId: Long): Optional<Task> =
    taskRepository.findById(taskId)

  fun putTask(taskId: Long, newTask: Task): Optional<Task> =
    taskRepository.findById(taskId).map { currentTask ->
      val updatedTask: Task =
        currentTask
          .copy(
            title = newTask.title,
            description = newTask.description,
            status = newTask.status,
            startDate = newTask.startDate,
            priority = newTask.priority,
            dueDate = newTask.dueDate
          )
      taskRepository.save(updatedTask)
    }

  fun deleteTask(taskId: Long): Boolean =
    taskRepository.findById(taskId).map { task ->
      taskRepository.delete(task)
      true
    }.orElse(false)
}

Extra points... Update your TaskResource.kt

If you want to continue providing a RESTful API, we need to update the tasks resources:

package com.grekz.crudKotlin.resource

import com.grekz.crudKotlin.model.Task
import com.grekz.crudKotlin.service.TaskService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import javax.validation.Valid

@RestController
@RequestMapping("/v1/api/tasks")
class TaskResource(private val taskService: TaskService) {

  @GetMapping
  fun getTasks(): List<Task> =
    taskService.getTasks()

  @PostMapping
  fun addTask(@Valid @RequestBody task: Task): ResponseEntity<Task> =
    ResponseEntity.ok(taskService.addTask(task))

  @GetMapping("/{id}")
  fun getTaskById(@PathVariable(value = "id") taskId: Long): ResponseEntity<Task> =
    taskService.getTaskById(taskId).map { task ->
      ResponseEntity.ok(task)
    }.orElse(ResponseEntity.notFound().build())

  @PutMapping("/{id}")
  fun updateTaskById(
    @PathVariable(value = "id") taskId: Long,
    @Valid @RequestBody newTask: Task): ResponseEntity<Task> =
    taskService.putTask(taskId, newTask)
      .map { task -> ResponseEntity.ok().body(task) }
      .orElse(ResponseEntity.notFound().build())

  @DeleteMapping("/{id}")
  fun deleteTask(@PathVariable(value = "id") taskId: Long): ResponseEntity<Void> =
    if (taskService.deleteTask(taskId))
      ResponseEntity<Void>(HttpStatus.ACCEPTED)
    else
      ResponseEntity.notFound().build()
}

Update the application yaml file

## ../resources/application.yml
## ... Previous config ...
graphql:
  servlet:
    mapping: /graphql
    enabled: true
    corsEnabled: true
graphiql:
  mapping: /graphiql
  endpoint: /graphql
  enabled: true
  pageTitle: GraphiQL
  cdn:
    enabled: false
    version: 0.11.11

Update your build.gradle.kts

// ... Previous config ...

dependencies {
    implementation("com.graphql-java:graphql-java-extended-scalars:1.0.1")
    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:voyager-spring-boot-starter:5.0.2")
    implementation("com.graphql-java:graphql-java-tools:5.2.4")
    // ... Rest of the dependencied ...
}

Add your GraphQL Schema

File: ../resources/schema.graphqls

type Query {
  getTasks: [Task]
  getTaskById(taskId: ID!): Task
}

type Mutation {
  createTask(
    title: String
    description: String
    status: Int
    priority: Int
  ): Task
  updateTask(
    taskId: Long!
    title: String
    description: String
    status: Int
    priority: Int
  ): Task
  deleteTask(taskId: Long!): Boolean
}

type Task {
  id: Long
  title: String
  description: String
  status: Int
  priority: Int
}

Add the resolvers

We need to tell the application how to handle the graphql requests. For queries and mutations.
Query Resolver:

// .../kotlin/com/grekz/crudKotlin/resolver/QueryResolver.kt
package com.grekz.crudKotlin.resolver

import com.coxautodev.graphql.tools.GraphQLQueryResolver
import com.grekz.crudKotlin.model.Task
import com.grekz.crudKotlin.service.TaskService
import org.springframework.stereotype.Component

@Component
class QueryResolver (private val taskService: TaskService) : GraphQLQueryResolver {
  fun getTasks(): List<Task> = taskService.getTasks()
  fun getTaskById(taskId: Long): Task = taskService.getTaskById(taskId).orElse(null)
}

Mutation Resolver:

// ../kotlin/com/grekz/crudKotlin/resolver/MutationResolver.kt
package com.grekz.crudKotlin.resolver

import com.coxautodev.graphql.tools.GraphQLMutationResolver
import com.grekz.crudKotlin.model.Task
import com.grekz.crudKotlin.service.TaskService
import org.springframework.stereotype.Component

@Component
class MutationResolver (private val taskService: TaskService) : GraphQLMutationResolver {
  fun createTask(title: String, description: String, status: Int, priority: Int): Task =
    taskService.addTask(
      Task(
        title = title,
        description = description,
        status = status,
        priority = priority
      )
    )

  fun updateTask(taskId: Long, title: String, description: String, status: Int, priority: Int): Task =
    taskService.putTask(
      taskId,
      Task(
        id = taskId,
        title = title,
        description = description,
        status = status,
        priority = priority
      )
    ).orElse(null)

  fun deleteTask(taskId: Long): Boolean = taskService.deleteTask(taskId)
}

It's all good and dandy... but just take me to the code!

Alright, if you just want to run the project you can get it in this repo: https://github.com/Grekz/example-kotlin-crud-graphql

How to test it?

CRUD tests:
Now you can test your changes on your API and go to the Graph_i_QL http://localhost:8080/graphiql

Create:

mutation {
  createTask(
    title: "He is cool Update"
    description: "asdas"
    status: 1
    priority: 2
  ) {
    id
    title
    description
    status
  }
}

Read:
All tasks

query {
  getTasks {
    id
    title
    description
    status
    priority
  }
}

Or task by ID

query {
  getTaskById(taskId: 1) {
    id
    title
    description
    status
    priority
  }
}

Update:

mutation {
  updateTask(
    taskId: 4
    title: "He is cool Update"
    description: "asdas"
    status: 1
    priority: 2
  ) {
    id
    title
    description
    status
  }
}

Delete:

mutation {
  deleteTask(taskId: 4)
}

Cheers!
I hope this helps you get your CRUD graphQL API up and running.
Let me know if you have some feedback!

Discussion (1)

Collapse
darkes profile image
Victor Darkes

Great write up! Always supportive of more Kotlin content on Dev. 👌