DEV Community

Cover image for BUILD YOUR FIRST SPRING BOOT(KOTLIN) BACK-END
prabhu
prabhu

Posted on • Originally published at blog.prabhuls.me

BUILD YOUR FIRST SPRING BOOT(KOTLIN) BACK-END

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. File → Project Structure → Project → SDK

  2. 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,
)
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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 code

  • JpaRepository<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 (Long in 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"
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

Class-Level Annotations:

  • @RestController — Combines @Controller and @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/5id = 5)

  • @RequestBody — Maps the HTTP request body (JSON) to a Kotlin object automatically

Endpoint Breakdown:

  • getAll() — Returns all todos using the built-in findAll() method

  • create() — Accepts a ToDo object and saves it to the database

  • patch() — Updates specific fields of an existing todo; uses Kotlin's ?.let to update only non-null fields

  • delete() — 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
)
Enter fullscreen mode Exit fullscreen mode

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>
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

  1. Locate the main application file (usually named TodoApplication.kt)

  2. Look for the main() function with @SpringBootApplication annotation

  3. Click the green Run button (▶️) next to the main function or class name

  4. 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
Enter fullscreen mode Exit fullscreen mode

For Windows:

gradlew.bat bootRun
Enter fullscreen mode Exit fullscreen mode

Method 3: Using the Gradle Task Panel

  1. Open the Gradle panel on the right side of your IDE

  2. Navigate to Tasks → application

  3. 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
Enter fullscreen mode Exit fullscreen mode

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

  1. Open your browser and go to http://localhost:8080/h2

  2. Enter the following connection details:

* **JDBC URL:** `jdbc:h2:file:./data/todo-db`

* **User Name:** (leave empty)

* **Password:** (leave empty)
Enter fullscreen mode Exit fullscreen mode
  1. 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"
}
Enter fullscreen mode Exit fullscreen mode

Expected Response (200 OK):

{
  "id": 1,
  "title": "Completed Spring Boot Tutorial",
  "date": "2025-12-01",
  "time": "14:30",
  "completed": true,
  "completedDate": "2025-11-30"
}
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

Expected Response:

"3 deleted"
Enter fullscreen mode Exit fullscreen mode

9. Delete All Todos (DELETE)

Endpoint: DELETE http://localhost:8080/deleteall

Expected Response:

"5 todos deleted"
Enter fullscreen mode Exit fullscreen mode

Testing Tips

  1. Test in Order: Start with POST to create data, then test GET, PATCH, and DELETE operations

  2. Check H2 Console: After each operation, verify changes in the database console (http://localhost:8080/h2)

  3. View Logs: Watch the console for SQL queries if spring.jpa.show-sql=true is enabled

  4. Use 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 @Valid annotations to ensure data integrity

  • Error Handling — Implement @ControllerAdvice for global exception handling with proper HTTP status codes

  • Production 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.

Top comments (0)