What to Expect from This Session
Create a new Spring Boot project from scratch
Understand the basic structure of a Spring Boot application
Set up a persistent in-memory database (H2)
Write and run basic SQL queries to manipulate data
Call your API endpoints to interact with the application
Prerequisites
Basic Kotlin or Java understanding (classes and objects)
REST API basics understanding
Familiarity with SQL SELECT commands and conditions
IntelliJ IDEA or Android Studio (both are essentially the same)
API Client: Postman (standard choice) or Bruno (open-source alternative)
Checkout my github for full source code.
Initialize Project
The easiest way to bootstrap your Spring Boot (Kotlin) application is by using the official Spring Initializer.
We are going to create a TODO list back-end.
Go to start.spring.io.
Choose the following configuration as shown above:
- Dependencies:
1. **Spring Web** — RESTful API support
2. **H2 Database** — In-memory database (no external database required)
3. **Spring Data JPA** — Data manipulation library for database operations
Click the Generate button to download a zip file. Extract the contents to your project folder and open it in your IDE.
Final Check
After importing the folder into your IDE, verify that you've configured the Java version to match the one you selected in Spring Initializer:
File → Project Structure → Project → SDK
File → Settings → Build Tools → Gradle → Gradle JVM
Project Structure
Object
A data class that contains the following task attributes:
id — Auto-incrementing identifier
title — Task name
date — Task due date
time — Task due time (optional)
completed — Status (boolean: completed or not)
completedDate — Date when the task was completed
//ToDo.kt
-----------------------------------------------------------------------------------------
@jakarta.persistence.Entity
data class ToDo(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
var title: String,
@JsonFormat(pattern = "yyyy-MM-dd")
var date: java.time.LocalDate = java.time.LocalDate.now(), // Default due date is today
@JsonFormat(pattern = "HH:mm")
var time : java.time.LocalTime? = null,
var completed: Boolean = false,
var completedDate: java.time.LocalDate? = null,
)
Explanation:
@Entity — Marks this class as a JPA entity, mapping it to a database table
@id — Designates the primary key field
@GeneratedValue — Configures automatic ID generation using the database's identity column
@JsonFormat — Specifies the JSON serialization format for date and time fields
Repository
//ToDoRepository.kt
------------------------------------------------------------------------------------------
@Repository
interface ToDoRepository : JpaRepository<ToDo, Long> {
@Query(
value = "SELECT * FROM to_do ORDER BY date ASC, time ASC",
nativeQuery = true
)
fun SortAllbyDateAndTime(): List<ToDo>
@Query(
value = "SELECT * FROM to_do WHERE completed=true ORDER BY date ASC, time ASC",
nativeQuery = true
)
fun Completedtsk(): List<ToDo>
@Query(
value = "SELECT * FROM to_do WHERE completed=false ORDER BY date ASC, time ASC",
nativeQuery = true
)
fun PendingTask(): List<ToDo>
@Modifying
@Transactional
@Query(
value = "DELETE FROM to_do",
nativeQuery = true
)
fun deleteAllTodos() : Int
@Modifying
@Transactional
@Query(
value = "DELETE FROM to_do WHERE id IN (:id)",
nativeQuery = true
)
fun BulkDeleteById(id: List<Long>) : Int
}
Explanation:
ToDoRepository extends JpaRepository — This inheritance grants automatic access to powerful CRUD operations including
findAll(),save(),delete(),findById(),count(), and many more without writing any implementation codeJpaRepository<ToDo, Long> — Takes two generic type parameters: first is the entity class (
ToDo) you want to manage, and second is the data type of the entity's primary key (Longin our case)@Query — Enables you to write custom native SQL queries or JPQL queries and bind them to repository method declarations
@Modifying and @Transactional — Required pair of annotations for any query that performs INSERT, UPDATE, or DELETE operations to ensure proper transaction management and data consistency
REST Controller
//ToDocontroller.kt
--------------------------------------------------------------------------------------------
@RestController
@CrossOrigin(origins = ["*"])
@RequestMapping("/")
class ToDoController(private val repo : ToDoRepository) {
@GetMapping
fun getAll() : List<ToDo> = repo.findAll()
@PostMapping
fun create(@RequestBody toDo: ToDo): ToDo {
return repo.save(toDo)
}
@PatchMapping("/{id}")
fun patch(@PathVariable id: Long, @RequestBody body: ToDoPatch): ToDo {
val todo = repo.findById(id).orElseThrow()
body.title?.let { todo.title = it }
body.date?.let { todo.date = it }
body.time?.let { todo.time = it }
body.completed?.let { newValue ->
if (!todo.completed && newValue) todo.completedDate = LocalDate.now()
if (todo.completed && !newValue) todo.completedDate = null
todo.completed = newValue
}
return repo.save(todo)
}
@DeleteMapping("/{id}")
fun delete(@PathVariable id: Long) {
repo.deleteById(id)
}
// Get all todos sorted by date and time
@GetMapping("/bytime")
fun getSortedTodos() : List<ToDo> = repo.SortAllbyDateAndTime()
// Get todos grouped by status (incomplete first, then completed)
@GetMapping("/completed")
fun completeTask() : List<ToDo> = repo.Completedtsk()
@GetMapping("/pending")
fun pendingTask() : List<ToDo> = repo.PendingTask()
@DeleteMapping("/deleteall")
fun deleteAll(): String {
val deletecount = repo.deleteAllTodos()
return "$deletecount todos deleted"
}
@DeleteMapping("/bulkdelete")
fun deleteById(@RequestBody body: ToDoBulkDelete): String {
val deleteCount = repo.BulkDeleteById(body.ids)
return "$deleteCount deleted"
}
}
Explanation:
Class-Level Annotations:
@RestController — Combines
@Controllerand@ResponseBody, indicating this class handles HTTP requests and returns JSON responses automatically@CrossOrigin — Enables Cross-Origin Resource Sharing (CORS), allowing requests from any origin (use specific origins in production)
@RequestMapping("/") — Sets the base URL path for all endpoints in this controller (root path in this case)
HTTP Method Annotations:
@GetMapping — Handles HTTP GET requests (retrieve data)
@PostMapping — Handles HTTP POST requests (create new resources)
@PatchMapping — Handles HTTP PATCH requests (partial updates)
@DeleteMapping — Handles HTTP DELETE requests (remove resources)
Parameter Annotations:
@PathVariable — Extracts values from the URL path (e.g.,
/delete/5→id = 5)@RequestBody — Maps the HTTP request body (JSON) to a Kotlin object automatically
Endpoint Breakdown:
getAll() — Returns all todos using the built-in
findAll()methodcreate() — Accepts a
ToDoobject and saves it to the databasepatch() — Updates specific fields of an existing todo; uses Kotlin's
?.letto update only non-null fieldsdelete() — Removes a single todo by ID
getSortedTodos() — Returns todos ordered by date and time
completeTask() / pendingTask() — Filters todos by completion status
deleteAll() — Removes all todos and returns the count
deleteById() — Bulk deletion of multiple todos by their IDs
DTO (Data Transfer Object)
Explanation
DTOs are lightweight data structures that transfer specific data between client and server without exposing the entire entity. They enable partial updates (sending only fields to modify), controlled data exposure (hiding sensitive fields), and security (preventing modification of immutable fields like id). Each operation can have its own DTO, making the API clearer and more flexible.
DTO Implementations:
ToDoPatch — Used for partial updates via PATCH requests. All fields are nullable, allowing clients to update only the fields they specify:
data class ToDoPatch(
val date: LocalDate? = null,
val time: LocalTime? = null,
val title: String? = null,
val completed: Boolean? = null
)
ToDoBulkDelete — Used for bulk deletion operations, accepting a list of IDs to delete multiple todos in a single request:
data class ToDoBulkDelete(
val ids: List<Long>
)
By using these DTOs, the API remains flexible, secure, and easy to understand for developers consuming it.
Application Configuration
application.properties
Configure your Spring Boot application by creating or editing the application.properties file in the src/main/resources directory:
# Application name
spring.application.name=todo
# Server port (default is 8080)
server.port=8080
# H2 Database configuration - File-based for data persistence
# Data persists even after server restart in ./data/todo-db directory
spring.datasource.url=jdbc:h2:file:./data/todo-db;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1
# Database credentials (empty for H2 default)
spring.datasource.username=
spring.datasource.password=
# Database driver
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# JPA/Hibernate configuration
# 'update' automatically creates/updates tables based on entity classes
spring.jpa.hibernate.ddl-auto=update
# Enable SQL query logging in console (useful for debugging)
spring.jpa.show-sql=true
# H2 Web Console configuration
# Access at: http://localhost:8080/h2
spring.h2.console.enabled=true
spring.h2.console.path=/h2
Key Configuration Explained:
server.port — Defines which port the application runs on (default: 8080)
spring.datasource.url — Uses file-based H2 database for persistence; data survives server restarts
spring.jpa.hibernate.ddl-auto=update — Automatically creates or updates database schema based on entity classes
spring.h2.console.enabled=true — Enables web-based database console for direct SQL queries and table inspection
spring.jpa.show-sql=true — Logs all SQL statements to console, helpful for learning and debugging
Running Your Application
Start the Application
There are multiple ways to run your Spring Boot application:
Method 1: Using IDE (Recommended for Beginners)
Locate the main application file (usually named
TodoApplication.kt)Look for the
main()function with@SpringBootApplicationannotationClick the green Run button (▶️) next to the main function or class name
-
Alternatively, right-click on the file and select Run 'TodoApplicationKt'
Method 2: Using Gradle (Command Line)
Open the terminal in your project root directory and run:
./gradlew bootRun
For Windows:
gradlew.bat bootRun
Method 3: Using the Gradle Task Panel
Open the Gradle panel on the right side of your IDE
Navigate to Tasks → application
Double-click on bootRun
Verify the Application is Running
Once the application starts, you should see console output similar to this:
Tomcat started on port(s): 8080 (http)
Started TodoApplicationKt in X.XXX seconds
Quick Health Check
Open your browser and navigate to:
API Base URL:
http://localhost:8080/H2 Console:
http://localhost:8080/h2
If you see a JSON response (likely an empty array []) at the base URL, your application is running successfully!
Accessing the H2 Database Console
Open your browser and go to
http://localhost:8080/h2Enter the following connection details:
* **JDBC URL:** `jdbc:h2:file:./data/todo-db`
* **User Name:** (leave empty)
* **Password:** (leave empty)
- Click Connect
You can now run SQL queries directly to view your TO_DO table and inspect the data.
Stopping the Application
In IDE: Click the red Stop button **** in the Run panel
In Terminal: Press
Ctrl + C
Testing Your API
Now that your application is running, let's test all the endpoints using Postman or Bruno. Make sure your server is running on http://localhost:8080.
1. Create a Todo (POST)
Endpoint: POST http://localhost:8080/
2. Get All Todos (GET)
Endpoint: GET http://localhost:8080/
Expected Response (200 OK):
3. Get Todos Sorted by Date and Time (GET)
Endpoint: GET http://localhost:8080/bytime
Returns all todos ordered by date and time (earliest first).
4. Get Pending Todos (GET)
Endpoint: GET http://localhost:8080/pending
Returns only incomplete todos (completed: false).
5. Get Completed Todos (GET)
Endpoint: GET http://localhost:8080/completed
Returns only completed todos (completed: true).
6. Update a Todo (PATCH)
Endpoint: PATCH http://localhost:8080/1
Request Body (JSON): (Include only fields you want to update)
{
"completed": true,
"title": "Completed Spring Boot Tutorial"
}
Expected Response (200 OK):
{
"id": 1,
"title": "Completed Spring Boot Tutorial",
"date": "2025-12-01",
"time": "14:30",
"completed": true,
"completedDate": "2025-11-30"
}
7. Delete a Single Todo (DELETE)
Endpoint: DELETE http://localhost:8080/1
Expected Response: 200 OK (no content)
8. Bulk Delete Todos (DELETE)
Endpoint: DELETE http://localhost:8080/bulkdelete
Request Body (JSON):
{
"ids": [1, 2, 3]
}
Expected Response:
"3 deleted"
9. Delete All Todos (DELETE)
Endpoint: DELETE http://localhost:8080/deleteall
Expected Response:
"5 todos deleted"
Testing Tips
Test in Order: Start with POST to create data, then test GET, PATCH, and DELETE operations
Check H2 Console: After each operation, verify changes in the database console (
http://localhost:8080/h2)View Logs: Watch the console for SQL queries if
spring.jpa.show-sql=trueis enabledUse Collections: In Postman/Bruno, organize these requests in a collection for easy testing
Next Steps & Improvements
Congratulations on building your first Spring Boot REST API! Here are ways to take it further:
Input Validation — Add
@NotBlank,@Size, and@Validannotations to ensure data integrityError Handling — Implement
@ControllerAdvicefor global exception handling with proper HTTP status codesProduction Database — Migrate from H2 to PostgreSQL, MySQL or MongoDB for production use
User Management — Add User entity with relationships to todos for multi-user support
Security — Add Spring Security with JWT authentication and role-based access control
API Documentation — Integrate Swagger/OpenAPI for interactive API documentation
Conclusion
You've successfully built a fully functional Spring Boot REST API with Kotlin! You learned to create entities, repositories, controllers, configure databases, and test endpoints.
thanks for reading, if you have any suggestion don’t hesitate to contact me.


![[]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fls-prabhu%2Ftaskbackend%2Frefs%2Fheads%2Fmaster%2Fimages%2Fimg.png%2520align%3D)
![[]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fls-prabhu%2Ftaskbackend%2Frefs%2Fheads%2Fmaster%2Fimages%2Fpostreq.png%2520align%3D)
![[]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fls-prabhu%2Ftaskbackend%2Frefs%2Fheads%2Fmaster%2Fimages%2Fgetreq.png%2520align%3D)
Top comments (0)