# Beginner's Guide: Creating a Spring Boot REST API with Kotlin

## 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](https://medium.com/@riztech.dev/simplifying-kotlin-classes-objects-and-constructors-48ce681588a5) or Java understanding (classes and objects)
    
* [REST API](https://medium.com/@techwritershub/the-basics-of-api-for-beginners-a-complete-guide-be4d0c9d9b15) basics understanding
    
* Familiarity with SQL SELECT commands and conditions
    
* [IntelliJ IDEA](https://www.jetbrains.com/idea/download/) or [Android Studio](https://developer.android.com/studio) (both are essentially the same)
    
* API Client: [Postman](https://www.postman.com/downloads/) (standard choice) or [Bruno](https://www.usebruno.com/downloads) (open-source alternative)
    
* Checkout my [github](https://github.com/ls-prabhu/taskbackend/tree/master) 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](http://start.spring.io).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1764325344854/7e437009-f9ab-434a-b429-f6405b608c7c.png align="left")

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:

1. **File → Project Structure → Project → SDK**
    
2. **File → Settings → Build Tools → Gradle → Gradle JVM**
    

```mermaid
classDiagram
direction TB

%% =======================
%% Application Root
%% =======================
class TodoApplicationKt {
  + main(String[]) Unit
}

%% =======================
%% Controller Layer
%% =======================
class ToDoController {
  + ToDoController(ToDoRepository)
  + create(ToDo) ToDo
  + completeTask() List~ToDo~
  + pendingTask() List~ToDo~
  + patch(Long, ToDoPatch) ToDo
  + delete(Long) Unit
  + deleteAll() String
  + deleteById(ToDoBulkDelete) String
  ---
  List~ToDo~ all
  List~ToDo~ sortedTodos
}

%% =======================
%% Repository Layer
%% =======================
class ToDoRepository {
  <<interface>>
  + completedTask() List~ToDo~
  + sortAllByDateAndTime() List~ToDo~
  + bulkDeleteById(List~Long~) Int
  + pendingTask() List~ToDo~
  + deleteAllTodos() Int
}

%% =======================
%% Data / DTOs
%% =======================
class ToDo {
  + ToDo(Long, String, LocalDate, LocalTime?, Boolean, LocalDate?)
  ---
  LocalDate date
  LocalTime? time
  String title
  LocalDate? completedDate
  Long id
  Boolean completed
}

class ToDoBulkDelete {
  + ToDoBulkDelete(List~Long~)
  ---
  List~Long~ ids
}

class ToDoPatch {
  + ToDoPatch(LocalDate?, LocalTime?, String?, Boolean?)
  + ToDoPatch()
  ---
  LocalDate? date
  LocalTime? time
  String? title
  Boolean? completed
}

%% =======================
%% Relationships
%% =======================
ToDoController --> ToDoRepository : uses
ToDoController --> ToDoPatch : input
ToDoController --> ToDoBulkDelete : input
ToDoController --> ToDo : returns
```

## Project Structure

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1764414804501/cbfd1cd6-b2de-4aa4-b409-a3933b92b7c7.png align="center")

# Data Object Creation

## 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
    

```kotlin
//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

```kotlin
//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 code
    
* **JpaRepository&lt;ToDo, Long&gt;** — 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

```kotlin

//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 `@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/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()` 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:

```kotlin
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:

```kotlin
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:

```kotlin
# 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)

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'**
    
    ![[]](https://raw.githubusercontent.com/ls-prabhu/taskbackend/refs/heads/master/images/img.png align="left")
    

### Method 2: Using Gradle (Command Line)

Open the terminal in your project root directory and run:

```bash
./gradlew bootRun
```

For Windows:

```bash
gradlew.bat bootRun
```

### 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:

```kotlin
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

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)
        
3. 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/`

![[]](https://raw.githubusercontent.com/ls-prabhu/taskbackend/refs/heads/master/images/postreq.png align="left")

## 2\. Get All Todos (GET)

**Endpoint:** `GET http://localhost:8080/`

**Expected Response (200 OK):**

![[]](https://raw.githubusercontent.com/ls-prabhu/taskbackend/refs/heads/master/images/getreq.png align="left")

## 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)*

```json
{
  "completed": true,
  "title": "Completed Spring Boot Tutorial"
}
```

**Expected Response (200 OK):**

```json
{
  "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):**

```json
{
  "ids": [1, 2, 3]
}
```

**Expected Response:**

```json
"3 deleted"
```

## 9\. Delete All Todos (DELETE)

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

**Expected Response:**

```json
"5 todos deleted"
```

## 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](https://beacons.ai/prabhuls).
