Kotlin Spring Boot | REST API 서버 만들기
이 글의 핵심
Kotlin Spring Boot에 대한 실전 가이드입니다. REST API 서버 만들기 등을 예제와 함께 설명합니다.
들어가며
Spring Boot는 Java 기반이지만 Kotlin으로 작성해도 동일한 스타터·빈을 그대로 씁니다. build.gradle.kts에서 의존성을 선언하고, 서비스·리포지토리를 설계도(클래스)에 맞춰 나누는 흐름이 일반적입니다.
1. Spring Boot 프로젝트 설정
build.gradle.kts
plugins {
kotlin("jvm") version "1.9.0"
kotlin("plugin.spring") version "1.9.0"
kotlin("plugin.jpa") version "1.9.0"
id("org.springframework.boot") version "3.2.0"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("com.h2database:h2")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
}
Application.kt
package com.example.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class Application
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:h2:mem:testdb
jpa:
hibernate:
ddl-auto: update
show-sql: true
2. REST Controller
UserController
package com.example.demo.controller
import com.example.demo.model.User
import com.example.demo.service.UserService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/api/users")
class UserController(private val userService: UserService) {
@GetMapping
fun getUsers(): List<User> {
return userService.findAll()
}
@GetMapping("/{id}")
fun getUser(@PathVariable id: Long): ResponseEntity<User> {
val user = userService.findById(id)
return if (user != null) {
ResponseEntity.ok(user)
} else {
ResponseEntity.notFound().build()
}
}
@PostMapping
fun createUser(@RequestBody user: User): ResponseEntity<User> {
val created = userService.save(user)
return ResponseEntity.status(HttpStatus.CREATED).body(created)
}
@PutMapping("/{id}")
fun updateUser(
@PathVariable id: Long,
@RequestBody user: User
): ResponseEntity<User> {
val updated = userService.update(id, user)
return if (updated != null) {
ResponseEntity.ok(updated)
} else {
ResponseEntity.notFound().build()
}
}
@DeleteMapping("/{id}")
fun deleteUser(@PathVariable id: Long): ResponseEntity<Void> {
val deleted = userService.delete(id)
return if (deleted) {
ResponseEntity.noContent().build()
} else {
ResponseEntity.notFound().build()
}
}
}
3. JPA Entity
User 엔티티
package com.example.demo.model
import jakarta.persistence.*
@Entity
@Table(name = "users")
data class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Column(nullable = false)
val name: String,
@Column(nullable = false, unique = true)
val email: String,
val age: Int? = null
)
4. Repository
UserRepository
package com.example.demo.repository
import com.example.demo.model.User
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface UserRepository : JpaRepository<User, Long> {
fun findByEmail(email: String): User?
fun findByNameContaining(keyword: String): List<User>
fun findByAgeGreaterThan(age: Int): List<User>
}
5. Service Layer
UserService
package com.example.demo.service
import com.example.demo.model.User
import com.example.demo.repository.UserRepository
import org.springframework.stereotype.Service
@Service
class UserService(private val userRepository: UserRepository) {
fun findAll(): List<User> {
return userRepository.findAll()
}
fun findById(id: Long): User? {
return userRepository.findById(id).orElse(null)
}
fun save(user: User): User {
return userRepository.save(user)
}
fun update(id: Long, user: User): User? {
val existing = findById(id) ?: return null
val updated = existing.copy(
name = user.name,
email = user.email,
age = user.age
)
return userRepository.save(updated)
}
fun delete(id: Long): Boolean {
return if (userRepository.existsById(id)) {
userRepository.deleteById(id)
true
} else {
false
}
}
}
6. 실전 예제
예제: Todo API
// Todo.kt
@Entity
data class Todo(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val title: String,
val completed: Boolean = false
)
// TodoRepository.kt
@Repository
interface TodoRepository : JpaRepository<Todo, Long> {
fun findByCompleted(completed: Boolean): List<Todo>
}
// TodoService.kt
@Service
class TodoService(private val todoRepository: TodoRepository) {
fun findAll(): List<Todo> = todoRepository.findAll()
fun save(todo: Todo): Todo = todoRepository.save(todo)
fun toggle(id: Long): Todo? {
val todo = todoRepository.findById(id).orElse(null) ?: return null
val updated = todo.copy(completed = !todo.completed)
return todoRepository.save(updated)
}
}
// TodoController.kt
@RestController
@RequestMapping("/api/todos")
class TodoController(private val todoService: TodoService) {
@GetMapping
fun getTodos(): List<Todo> = todoService.findAll()
@PostMapping
fun createTodo(@RequestBody todo: Todo): ResponseEntity<Todo> {
val created = todoService.save(todo)
return ResponseEntity.status(HttpStatus.CREATED).body(created)
}
@PutMapping("/{id}/toggle")
fun toggleTodo(@PathVariable id: Long): ResponseEntity<Todo> {
val updated = todoService.toggle(id)
return if (updated != null) {
ResponseEntity.ok(updated)
} else {
ResponseEntity.notFound().build()
}
}
}
정리
핵심 요약
- Spring Boot: Kotlin 완벽 지원
- data class: Entity 정의 간편
- 생성자 주입: 불변성 보장
- JpaRepository: CRUD 자동 제공
- ResponseEntity: HTTP 응답 제어
Kotlin의 장점
- Null 안전성:
?연산자 - 간결한 문법: 보일러플레이트 감소
- 확장 함수: 유틸리티 추가 용이
- 코루틴: 비동기 처리 간편
다음 단계
- Kotlin 고급 기능
- Kotlin 테스팅
- Java Spring Boot
관련 글
- Java Spring Boot | REST API 서버 만들기
- C++ HTTP 클라이언트 완벽 가이드 | REST API 호출·연결 풀·타임아웃·프로덕션 패턴
- C++ JSON 파싱 완벽 가이드 | nlohmann·RapidJSON·커스텀 타입·에러 처리·프로덕션 패턴
- C++ REST API 클라이언트 완벽 가이드 | CRUD·인증·에러 처리·프로덕션 패턴 [#21-3]
- [Go 2주 완성 #08] Day 14: 실전 미니 프로젝트 - REST API 서버 구축