<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[CraftWithCode]]></title><description><![CDATA[Backend, Android, and automation guides with real code and architecture insights. Clear, reusable, quality-focused content.]]></description><link>https://blog.prabhuls.me</link><generator>RSS for Node</generator><lastBuildDate>Thu, 14 May 2026 14:53:05 GMT</lastBuildDate><atom:link href="https://blog.prabhuls.me/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[A Better Way to Access Your Home Network]]></title><description><![CDATA[What You Get
Before diving in, here's a quick summary of what this stack gives you:

No ads or trackers: blocks all ads and trackers at the DNS level across every devices even when you are not in home]]></description><link>https://blog.prabhuls.me/way-to-access-your-home-network</link><guid isPermaLink="true">https://blog.prabhuls.me/way-to-access-your-home-network</guid><category><![CDATA[self-hosted]]></category><category><![CDATA[Private Cloud]]></category><category><![CDATA[ad-free internet]]></category><category><![CDATA[adguardhome]]></category><category><![CDATA[tailscale]]></category><category><![CDATA[nginx proxy manager]]></category><dc:creator><![CDATA[Prabhu L S]]></dc:creator><pubDate>Thu, 14 May 2026 12:37:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/683bd063e0f64e2ecc61946d/95816b7d-c01f-4d2e-8257-5796e335e7ce.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What You Get</h2>
<p>Before diving in, here's a quick summary of what this stack gives you:</p>
<ul>
<li><p><strong>No ads or trackers</strong>: blocks all ads and trackers at the DNS level across every devices even when you are not in home.</p>
</li>
<li><p><strong>Remote access to all devices</strong>: reach any device or service privately from anywhere, without exposing anything to the public internet.</p>
</li>
<li><p><strong>Custom DNS rewrites</strong>: use clean local domains like <code>paperless.home</code>, <code>grafana.lan</code> or <code>router.local</code> instead of raw IP:ports.</p>
</li>
<li><p>This is just about how everything works together, not a setup guide don't worry if you need a <a href="http://buymeacoffee.com/prabhuls/e/538215">download it here</a></p>
</li>
</ul>
<h2>How the Stack Fits Together</h2>
<img src="https://cdn.hashnode.com/uploads/covers/683bd063e0f64e2ecc61946d/6abb6d4e-b55f-4442-8f31-f845d205f5d3.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Tailscale</strong> creates the encrypted tunnel that connects your devices securely.</p>
</li>
<li><p><strong>AdGuard Home</strong> handles DNS resolution, ad blocking, and custom domain rewrites.</p>
</li>
<li><p><strong>Nginx Proxy Manager</strong> routes incoming requests to the right service and issues SSL certificates automatically.</p>
</li>
</ul>
<p>Together, they give you a clean, private network that works from anywhere — with no public exposure.</p>
<hr />
<h2>1. Tailscale</h2>
<ul>
<li><p>Tailscale is a mesh VPN built on the <a href="https://www.wireguard.com/">WireGuard</a> protocol. It creates encrypted, peer-to-peer tunnels between your devices.</p>
</li>
<li><p>Unlike traditional VPNs that route all traffic through a central server, Tailscale connects devices directly to each other whenever possible, giving you low-latency, high-performance private networking.</p>
</li>
<li><p>Instead of exposing services directly to the internet or configuring complicated port forwarding rules, Tailscale allows devices to communicate securely over encrypted tunnels.</p>
</li>
</ul>
<h3>Why Use TailScale</h3>
<ul>
<li><p><strong>Remote Access</strong>: Access your home network from anywhere without port forwarding or exposing services to the public internet</p>
</li>
<li><p><strong>Cross-Platform</strong>: Works on Linux, Windows, macOS, iOS, and Android</p>
</li>
<li><p><strong>Tailnet</strong>: Automatically creates a private network with all your devices</p>
</li>
<li><p><strong>MagicDNS</strong>: dedicated custom domain will be provided.</p>
</li>
<li><p><strong>Global DNS Control</strong>: Set a single DNS server for all connected devices. Point it to <strong>our AdGuard Home</strong> and every device instantly gets ad blocking and custom domains.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/683bd063e0f64e2ecc61946d/4906e57a-c31d-4a1a-8823-8ca8bc832dd3.png" alt="" style="display:block;margin:0 auto" />

<h3>Use Case: Private Access to Self-Hosted Services</h3>
<p>Instead of exposing apps to the public internet using services like ngrok or Cloudflare Tunnel:</p>
<pre><code class="language-plaintext"># Public (exposed, risky)
https://myserver.example.com
</code></pre>
<p>You can access them privately through Tailscale:</p>
<pre><code class="language-plaintext"># Via Tailscale IP
http://100.x.x.x:8080

# Via MagicDNS hostname
http://devicename.tailnet-name.ts.net:8080

# Via custom domain (with AdGuard + NPM — covered below)
http://plex.home
</code></pre>
<p>This dramatically reduces your attack surface while keeping everything accessible from your phone, laptop, or any other enrolled device — anywhere in the world.</p>
<hr />
<h2>2. Adgaurd Home</h2>
<p><strong>AdGuard Home</strong> is a network-wide ad blocker and DNS resolver. Rather than blocking ads in a browser, it blocks them at the DNS level meaning when any device on your network tries to resolve a tracker or ad domain, AdGuard simply returns nothing. The request never leaves your network.</p>
<img src="https://cdn.hashnode.com/uploads/covers/683bd063e0f64e2ecc61946d/edd6c8ff-8fb4-4ddd-8a99-6e776e38a73d.png" alt="" style="display:block;margin:0 auto" />

<h3>Why Use AdGuard?</h3>
<ul>
<li><p><strong>Reduce Telemetry</strong>: Many devices (Windows, smart appliances) send telemetry data home. Block these at the DNS level to reduce data leakage.</p>
</li>
<li><p><strong>Privacy Protection</strong>: Block trackers across all devices (phones, laptops, smart TVs) by <strong>not using ISP</strong> default resolvers.</p>
</li>
<li><p><strong>No Client Software</strong>: Works at the DNS level — no installation needed on individual devices</p>
</li>
<li><p><strong>Custom Blocklists</strong>: Add your own domains or use community blocklists</p>
</li>
<li><p><strong>Adult Content Filtering</strong>: Family-safe DNS options</p>
</li>
<li><p><strong>Statistics</strong>: See what domains are being queried on your network</p>
</li>
<li><p><strong>DNS rewriting</strong>: assign custom domain for rules.</p>
</li>
</ul>
<h3>DNS Rewrites</h3>
<p>This is what makes the whole stack elegant. In AdGuard Home, you can create DNS rewrite rules like:</p>
<pre><code class="language-plaintext">paperless.home  →  100.x.x.x   (your server's Tailscale IP)
grafana.home    →  100.x.x.x
portainer.home  →  100.x.x.x
</code></pre>
<p>Now, when you type <code>https://paperless.home</code> on any device connected to Tailscale (with AdGuard set as the DNS), it resolves to your server's private IP. NPM then handles routing that request to the right container.</p>
<hr />
<h2>3. Nginx Proxy Manager(NPM)</h2>
<p>Nginx Proxy Manager provides a clean web UI for managing Nginx reverse proxy configurations. Instead of writing Nginx config files by hand, you point and click NPM handles SSL certificates, redirects, and proxy rules automatically.</p>
<img src="https://cdn.hashnode.com/uploads/covers/683bd063e0f64e2ecc61946d/2873eb95-d64e-48b2-8cfb-fd9ebecfe579.png" alt="" style="display:block;margin:0 auto" />

<h3>How It Works with the Stack</h3>
<p>NPM sits between AdGuard and your actual services. When a request arrives for <code>paperless.home</code>:</p>
<ol>
<li><p>AdGuard resolves <code>paperless.home</code> → your server's Tailscale IP.</p>
</li>
<li><p>The request hits NPM on port 80(http)/443(https).</p>
</li>
<li><p>NPM reads the hostname, finds the matching proxy host, and forwards the request to the right Docker container (e.g., <code>paperless-ngx:8000</code>).</p>
</li>
<li><p>NPM handles the SSL termination, so your browser gets a valid HTTPS connection.</p>
</li>
</ol>
<hr />
<p>To build this setup, all you really need is:</p>
<ul>
<li><p>A Linux machine (old PC, mini PC, or VPS)</p>
</li>
<li><p>Docker</p>
</li>
<li><p>A bit of patience</p>
</li>
<li><p>And honestly… ChatGPT helps a lot.</p>
</li>
</ul>
<p>If you want the complete step-by-step setup guide with configuration examples, networking flow, DNS rewrites, and reverse proxy setup:</p>
<ul>
<li><p><a href="https://link.prabhuls.me/hJ7j7Al?utm_source=chatgpt.com">Download Guide (Direct)</a></p>
</li>
<li><p><a href="https://buymeacoffee.com/prabhuls/e/538215?utm_source=chatgpt.com">Mirror Download</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Beginner's Guide: Creating a Spring Boot REST API with Kotlin]]></title><description><![CDATA[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 A...]]></description><link>https://blog.prabhuls.me/build-rest-api-with-kotlin-spring-boot</link><guid isPermaLink="true">https://blog.prabhuls.me/build-rest-api-with-kotlin-spring-boot</guid><category><![CDATA[Kotlin]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[H2 Database]]></category><category><![CDATA[REST API]]></category><category><![CDATA[APIs]]></category><dc:creator><![CDATA[Prabhu L S]]></dc:creator><pubDate>Mon, 01 Dec 2025 02:30:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764471902074/d7af9420-61da-4fee-9e0a-2099f542a676.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-to-expect-from-this-session">What to Expect from This Session</h2>
<ul>
<li><p>Create a new <strong>Spring Boot project</strong> from scratch</p>
</li>
<li><p>Understand the <strong>basic structure</strong> of a Spring Boot application</p>
</li>
<li><p>Set up a <strong>persistent in-memory database (H2)</strong></p>
</li>
<li><p>Write and run basic <strong>SQL queries</strong> to manipulate data</p>
</li>
<li><p>Call your <strong>API</strong> endpoints to interact with the application</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>Basic <a target="_blank" href="https://medium.com/@riztech.dev/simplifying-kotlin-classes-objects-and-constructors-48ce681588a5">Kotlin</a> or Java understanding (classes and objects)</p>
</li>
<li><p><a target="_blank" href="https://medium.com/@techwritershub/the-basics-of-api-for-beginners-a-complete-guide-be4d0c9d9b15">REST API</a> basics understanding</p>
</li>
<li><p>Familiarity with SQL SELECT commands and conditions</p>
</li>
<li><p><a target="_blank" href="https://www.jetbrains.com/idea/download/">IntelliJ IDEA</a> or <a target="_blank" href="https://developer.android.com/studio">Android Studio</a> (both are essentially the same)</p>
</li>
<li><p>API Client: <a target="_blank" href="https://www.postman.com/downloads/">Postman</a> (standard choice) or <a target="_blank" href="https://www.usebruno.com/downloads">Bruno</a> (open-source alternative)</p>
</li>
<li><p>Checkout my <a target="_blank" href="https://github.com/ls-prabhu/taskbackend/tree/master">github</a> for full source code.</p>
</li>
</ul>
<h2 id="heading-initialize-project">Initialize Project</h2>
<p>The easiest way to bootstrap your Spring Boot (Kotlin) application is by using the official Spring Initializer.</p>
<p>We are going to create a TODO list back-end.</p>
<p>Go to <a target="_blank" href="http://start.spring.io">start.spring.io</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764325344854/7e437009-f9ab-434a-b429-f6405b608c7c.png" alt /></p>
<p>Choose the following configuration as shown above:</p>
<ul>
<li><p><strong>Dependencies:</strong></p>
<ol>
<li><p><strong>Spring Web</strong> — RESTful API support</p>
</li>
<li><p><strong>H2 Database</strong> — In-memory database (no external database required)</p>
</li>
<li><p><strong>Spring Data JPA</strong> — Data manipulation library for database operations</p>
</li>
</ol>
</li>
</ul>
<p>Click the <strong>Generate</strong> button to download a zip file. Extract the contents to your project folder and open it in your IDE.</p>
<h3 id="heading-final-check">Final Check</h3>
<p>After importing the folder into your IDE, verify that you've configured the Java version to match the one you selected in Spring Initializer:</p>
<ol>
<li><p><strong>File → Project Structure → Project → SDK</strong></p>
</li>
<li><p><strong>File → Settings → Build Tools → Gradle → Gradle JVM</strong></p>
</li>
</ol>
<pre><code class="lang-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 {
  &lt;&lt;interface&gt;&gt;
  + 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 --&gt; ToDoRepository : uses
ToDoController --&gt; ToDoPatch : input
ToDoController --&gt; ToDoBulkDelete : input
ToDoController --&gt; ToDo : returns
</code></pre>
<h2 id="heading-project-structure">Project Structure</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764414804501/cbfd1cd6-b2de-4aa4-b409-a3933b92b7c7.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-data-object-creation">Data Object Creation</h1>
<h2 id="heading-object">Object</h2>
<p>A data class that contains the following task attributes:</p>
<ul>
<li><p><strong>id</strong> — Auto-incrementing identifier</p>
</li>
<li><p><strong>title</strong> — Task name</p>
</li>
<li><p><strong>date</strong> — Task due date</p>
</li>
<li><p><strong>time</strong> — Task due time (optional)</p>
</li>
<li><p><strong>completed</strong> — Status (boolean: completed or not)</p>
</li>
<li><p><strong>completedDate</strong> — Date when the task was completed</p>
</li>
</ul>
<pre><code class="lang-kotlin"><span class="hljs-comment">//ToDo.kt</span>
-----------------------------------------------------------------------------------------
<span class="hljs-meta">@jakarta</span>.persistence.Entity
<span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ToDo</span></span>(
    <span class="hljs-meta">@Id</span>
    <span class="hljs-meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span>
    <span class="hljs-keyword">val</span> id: <span class="hljs-built_in">Long</span> = <span class="hljs-number">0</span>,
    <span class="hljs-keyword">var</span> title: String,
    <span class="hljs-meta">@JsonFormat(pattern = <span class="hljs-meta-string">"yyyy-MM-dd"</span>)</span>
    <span class="hljs-keyword">var</span> date: java.time.LocalDate = java.time.LocalDate.now(), <span class="hljs-comment">// Default due date is today</span>
    <span class="hljs-meta">@JsonFormat(pattern = <span class="hljs-meta-string">"HH:mm"</span>)</span>
    <span class="hljs-keyword">var</span> time : java.time.LocalTime? = <span class="hljs-literal">null</span>,
    <span class="hljs-keyword">var</span> completed: <span class="hljs-built_in">Boolean</span> = <span class="hljs-literal">false</span>,
    <span class="hljs-keyword">var</span> completedDate: java.time.LocalDate? = <span class="hljs-literal">null</span>,
)
</code></pre>
<h3 id="heading-explanation">Explanation:</h3>
<ul>
<li><p><strong>@Entity</strong> — Marks this class as a JPA entity, mapping it to a database table</p>
</li>
<li><p><strong>@Id</strong> — Designates the primary key field</p>
</li>
<li><p><strong>@GeneratedValue</strong> — Configures automatic ID generation using the database's identity column</p>
</li>
<li><p><strong>@JsonFormat</strong> — Specifies the JSON serialization format for date and time fields</p>
</li>
</ul>
<h1 id="heading-repository">Repository</h1>
<pre><code class="lang-kotlin"><span class="hljs-comment">//ToDoRepository.kt</span>
------------------------------------------------------------------------------------------
<span class="hljs-meta">@Repository</span>
<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ToDoRepository</span> : <span class="hljs-type">JpaRepository</span>&lt;<span class="hljs-type">ToDo, Long</span>&gt; </span>{
    <span class="hljs-meta">@Query(
        value = <span class="hljs-meta-string">"SELECT * FROM to_do ORDER BY date ASC, time ASC"</span>,
        nativeQuery = true
    )</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">SortAllbyDateAndTime</span><span class="hljs-params">()</span></span>: List&lt;ToDo&gt;

    <span class="hljs-meta">@Query(
        value = <span class="hljs-meta-string">"SELECT * FROM to_do WHERE completed=true ORDER BY date ASC, time ASC"</span>,
        nativeQuery = true
    )</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">Completedtsk</span><span class="hljs-params">()</span></span>: List&lt;ToDo&gt;

    <span class="hljs-meta">@Query(
        value = <span class="hljs-meta-string">"SELECT * FROM to_do WHERE completed=false ORDER BY date ASC, time ASC"</span>,
        nativeQuery = true
    )</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">PendingTask</span><span class="hljs-params">()</span></span>: List&lt;ToDo&gt;

    <span class="hljs-meta">@Modifying</span>
    <span class="hljs-meta">@Transactional</span>
    <span class="hljs-meta">@Query(
        value = <span class="hljs-meta-string">"DELETE FROM to_do"</span>,
        nativeQuery = true
    )</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">deleteAllTodos</span><span class="hljs-params">()</span></span> : <span class="hljs-built_in">Int</span>

    <span class="hljs-meta">@Modifying</span>
    <span class="hljs-meta">@Transactional</span>
    <span class="hljs-meta">@Query(
        value = <span class="hljs-meta-string">"DELETE FROM to_do WHERE id IN (:id)"</span>,
        nativeQuery = true
    )</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">BulkDeleteById</span><span class="hljs-params">(id: <span class="hljs-type">List</span>&lt;<span class="hljs-type">Long</span>&gt;)</span></span> : <span class="hljs-built_in">Int</span>
}
</code></pre>
<h2 id="heading-explanation-1">Explanation:</h2>
<ul>
<li><p><strong>ToDoRepository</strong> extends <strong>JpaRepository</strong> — This inheritance grants automatic access to powerful CRUD operations including <code>findAll()</code>, <code>save()</code>, <code>delete()</code>, <code>findById()</code>, <code>count()</code>, and many more without writing any implementation code</p>
</li>
<li><p><strong>JpaRepository&lt;ToDo, Long&gt;</strong> — Takes two generic type parameters: first is the entity class (<code>ToDo</code>) you want to manage, and second is the data type of the entity's primary key (<code>Long</code> in our case)</p>
</li>
<li><p><strong>@Query</strong> — Enables you to write custom native SQL queries or JPQL queries and bind them to repository method declarations</p>
</li>
<li><p><strong>@Modifying</strong> and <strong>@Transactional</strong> — Required pair of annotations for any query that performs INSERT, UPDATE, or DELETE operations to ensure proper transaction management and data consistency</p>
</li>
</ul>
<h1 id="heading-rest-controller">REST Controller</h1>
<pre><code class="lang-kotlin">
<span class="hljs-comment">//ToDocontroller.kt</span>
--------------------------------------------------------------------------------------------
<span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@CrossOrigin(origins = [<span class="hljs-meta-string">"*"</span>])</span>
<span class="hljs-meta">@RequestMapping(<span class="hljs-meta-string">"/"</span>)</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ToDoController</span></span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> repo : ToDoRepository) {

    <span class="hljs-meta">@GetMapping</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getAll</span><span class="hljs-params">()</span></span> : List&lt;ToDo&gt; = repo.findAll()

    <span class="hljs-meta">@PostMapping</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">create</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> toDo: <span class="hljs-type">ToDo</span>)</span></span>: ToDo {
        <span class="hljs-keyword">return</span> repo.save(toDo)
    }


    <span class="hljs-meta">@PatchMapping(<span class="hljs-meta-string">"/{id}"</span>)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">patch</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable</span> id: <span class="hljs-type">Long</span>, <span class="hljs-meta">@RequestBody</span> body: <span class="hljs-type">ToDoPatch</span>)</span></span>: ToDo {
    <span class="hljs-keyword">val</span> 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 -&gt;
        <span class="hljs-keyword">if</span> (!todo.completed &amp;&amp; newValue) todo.completedDate = LocalDate.now()
        <span class="hljs-keyword">if</span> (todo.completed &amp;&amp; !newValue) todo.completedDate = <span class="hljs-literal">null</span>
        todo.completed = newValue
    }

    <span class="hljs-keyword">return</span> repo.save(todo)
}

    <span class="hljs-meta">@DeleteMapping(<span class="hljs-meta-string">"/{id}"</span>)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">delete</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable</span> id: <span class="hljs-type">Long</span>)</span></span> {
        repo.deleteById(id)
    }

    <span class="hljs-comment">// Get all todos sorted by date and time</span>
    <span class="hljs-meta">@GetMapping(<span class="hljs-meta-string">"/bytime"</span>)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getSortedTodos</span><span class="hljs-params">()</span></span> : List&lt;ToDo&gt; = repo.SortAllbyDateAndTime()

    <span class="hljs-comment">// Get todos grouped by status (incomplete first, then completed)</span>
    <span class="hljs-meta">@GetMapping(<span class="hljs-meta-string">"/completed"</span>)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">completeTask</span><span class="hljs-params">()</span></span> : List&lt;ToDo&gt; = repo.Completedtsk()

    <span class="hljs-meta">@GetMapping(<span class="hljs-meta-string">"/pending"</span>)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">pendingTask</span><span class="hljs-params">()</span></span> : List&lt;ToDo&gt; = repo.PendingTask()

    <span class="hljs-meta">@DeleteMapping(<span class="hljs-meta-string">"/deleteall"</span>)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">deleteAll</span><span class="hljs-params">()</span></span>: String {
        <span class="hljs-keyword">val</span> deletecount = repo.deleteAllTodos()
        <span class="hljs-keyword">return</span> <span class="hljs-string">"<span class="hljs-variable">$deletecount</span> todos deleted"</span>
    }

    <span class="hljs-meta">@DeleteMapping(<span class="hljs-meta-string">"/bulkdelete"</span>)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">deleteById</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> body: <span class="hljs-type">ToDoBulkDelete</span>)</span></span>: String {
        <span class="hljs-keyword">val</span> deleteCount = repo.BulkDeleteById(body.ids)
        <span class="hljs-keyword">return</span> <span class="hljs-string">"<span class="hljs-variable">$deleteCount</span> deleted"</span>
    }
}
</code></pre>
<h2 id="heading-explanation-2">Explanation:</h2>
<h3 id="heading-class-level-annotations">Class-Level Annotations:</h3>
<ul>
<li><p><strong>@RestController</strong> — Combines <code>@Controller</code> and <code>@ResponseBody</code>, indicating this class handles HTTP requests and returns JSON responses automatically</p>
</li>
<li><p><strong>@CrossOrigin</strong> — Enables Cross-Origin Resource Sharing (CORS), allowing requests from any origin (use specific origins in production)</p>
</li>
<li><p><strong>@RequestMapping("/")</strong> — Sets the base URL path for all endpoints in this controller (root path in this case)</p>
</li>
</ul>
<h3 id="heading-http-method-annotations">HTTP Method Annotations:</h3>
<ul>
<li><p><strong>@GetMapping</strong> — Handles HTTP GET requests (retrieve data)</p>
</li>
<li><p><strong>@PostMapping</strong> — Handles HTTP POST requests (create new resources)</p>
</li>
<li><p><strong>@PatchMapping</strong> — Handles HTTP PATCH requests (partial updates)</p>
</li>
<li><p><strong>@DeleteMapping</strong> — Handles HTTP DELETE requests (remove resources)</p>
</li>
</ul>
<h3 id="heading-parameter-annotations">Parameter Annotations:</h3>
<ul>
<li><p><strong>@PathVariable</strong> — Extracts values from the URL path (e.g., <code>/delete/5</code> → <code>id = 5</code>)</p>
</li>
<li><p><strong>@RequestBody</strong> — Maps the HTTP request body (JSON) to a Kotlin object automatically</p>
</li>
</ul>
<h3 id="heading-endpoint-breakdown">Endpoint Breakdown:</h3>
<ul>
<li><p><strong>getAll()</strong> — Returns all todos using the built-in <code>findAll()</code> method</p>
</li>
<li><p><strong>create()</strong> — Accepts a <code>ToDo</code> object and saves it to the database</p>
</li>
<li><p><strong>patch()</strong> — Updates specific fields of an existing todo; uses Kotlin's <code>?.let</code> to update only non-null fields</p>
</li>
<li><p><strong>delete()</strong> — Removes a single todo by ID</p>
</li>
<li><p><strong>getSortedTodos()</strong> — Returns todos ordered by date and time</p>
</li>
<li><p><strong>completeTask() / pendingTask()</strong> — Filters todos by completion status</p>
</li>
<li><p><strong>deleteAll()</strong> — Removes all todos and returns the count</p>
</li>
<li><p><strong>deleteById()</strong> — Bulk deletion of multiple todos by their IDs</p>
</li>
</ul>
<h1 id="heading-dto-data-transfer-object">DTO (Data Transfer Object)</h1>
<h2 id="heading-explanation-3">Explanation</h2>
<p>DTOs are lightweight data structures that transfer specific data between client and server without exposing the entire entity. They enable <strong>partial updates</strong> (sending only fields to modify), <strong>controlled data exposure</strong> (hiding sensitive fields), and <strong>security</strong> (preventing modification of immutable fields like <code>id</code>). Each operation can have its own DTO, making the API clearer and more flexible.</p>
<h3 id="heading-dto-implementations">DTO Implementations:</h3>
<p><strong>ToDoPatch</strong> — Used for partial updates via PATCH requests. All fields are nullable, allowing clients to update only the fields they specify:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ToDoPatch</span></span>(
    <span class="hljs-keyword">val</span> date: LocalDate? = <span class="hljs-literal">null</span>,
    <span class="hljs-keyword">val</span> time: LocalTime? = <span class="hljs-literal">null</span>,
    <span class="hljs-keyword">val</span> title: String? = <span class="hljs-literal">null</span>,
    <span class="hljs-keyword">val</span> completed: <span class="hljs-built_in">Boolean</span>? = <span class="hljs-literal">null</span>
)
</code></pre>
<p><strong>ToDoBulkDelete</strong> — Used for bulk deletion operations, accepting a list of IDs to delete multiple todos in a single request:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ToDoBulkDelete</span></span>(
    <span class="hljs-keyword">val</span> ids: List&lt;<span class="hljs-built_in">Long</span>&gt;
)
</code></pre>
<p>By using these DTOs, the API remains flexible, secure, and easy to understand for developers consuming it.</p>
<h1 id="heading-application-configuration">Application Configuration</h1>
<h2 id="heading-applicationproperties">application.properties</h2>
<p>Configure your Spring Boot application by creating or editing the <code>application.properties</code> file in the <code>src/main/resources</code> directory:</p>
<pre><code class="lang-kotlin"># Application name
spring.application.name=todo

# Server port (default <span class="hljs-keyword">is</span> <span class="hljs-number">8080</span>)
server.port=<span class="hljs-number">8080</span>

# H2 Database configuration - File-based <span class="hljs-keyword">for</span> <span class="hljs-keyword">data</span> persistence
# Data persists even after server restart <span class="hljs-keyword">in</span> ./<span class="hljs-keyword">data</span>/todo-db directory
spring.datasource.url=jdbc:h2:file:./<span class="hljs-keyword">data</span>/todo-db;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-<span class="hljs-number">1</span>

# Database credentials (empty <span class="hljs-keyword">for</span> H2 default)
spring.datasource.username=
spring.datasource.password=

# Database driver
spring.datasource.driver-<span class="hljs-class"><span class="hljs-keyword">class</span>-<span class="hljs-title">name</span>=<span class="hljs-title">org</span>.<span class="hljs-title">h2</span>.<span class="hljs-title">Driver</span></span>
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# JPA/Hibernate configuration
# <span class="hljs-string">'update'</span> automatically creates/updates tables based on entity classes
spring.jpa.hibernate.ddl-auto=update

# Enable SQL query logging <span class="hljs-keyword">in</span> console (useful <span class="hljs-keyword">for</span> debugging)
spring.jpa.show-sql=<span class="hljs-literal">true</span>

# H2 Web Console configuration
# Access at: http:<span class="hljs-comment">//localhost:8080/h2</span>
spring.h2.console.enabled=<span class="hljs-literal">true</span>
spring.h2.console.path=/h2
</code></pre>
<h2 id="heading-key-configuration-explained">Key Configuration Explained:</h2>
<ul>
<li><p><strong>server.port</strong> — Defines which port the application runs on (default: 8080)</p>
</li>
<li><p><strong>spring.datasource.url</strong> — Uses file-based H2 database for persistence; data survives server restarts</p>
</li>
<li><p><strong>spring.jpa.hibernate.ddl-auto=update</strong> — Automatically creates or updates database schema based on entity classes</p>
</li>
<li><p><strong>spring.h2.console.enabled=true</strong> — Enables web-based database console for direct SQL queries and table inspection</p>
</li>
<li><p><strong>spring.jpa.show-sql=true</strong> — Logs all SQL statements to console, helpful for learning and debugging</p>
</li>
</ul>
<h1 id="heading-running-your-application">Running Your Application</h1>
<h2 id="heading-start-the-application">Start the Application</h2>
<p>There are multiple ways to run your Spring Boot application:</p>
<h3 id="heading-method-1-using-ide-recommended-for-beginners">Method 1: Using IDE (Recommended for Beginners)</h3>
<ol>
<li><p>Locate the main application file (usually named <code>TodoApplication.kt</code>)</p>
</li>
<li><p>Look for the <code>main()</code> function with <code>@SpringBootApplication</code> annotation</p>
</li>
<li><p>Click the green <strong>Run</strong> button (▶️) next to the main function or class name</p>
</li>
<li><p>Alternatively, right-click on the file and select <strong>Run 'TodoApplicationKt'</strong></p>
<p> <img src="https://raw.githubusercontent.com/ls-prabhu/taskbackend/refs/heads/master/images/img.png" alt="[]" /></p>
</li>
</ol>
<h3 id="heading-method-2-using-gradle-command-line">Method 2: Using Gradle (Command Line)</h3>
<p>Open the terminal in your project root directory and run:</p>
<pre><code class="lang-bash">./gradlew bootRun
</code></pre>
<p>For Windows:</p>
<pre><code class="lang-bash">gradlew.bat bootRun
</code></pre>
<h3 id="heading-method-3-using-the-gradle-task-panel">Method 3: Using the Gradle Task Panel</h3>
<ol>
<li><p>Open the <strong>Gradle</strong> panel on the right side of your IDE</p>
</li>
<li><p>Navigate to <strong>Tasks → application</strong></p>
</li>
<li><p>Double-click on <strong>bootRun</strong></p>
</li>
</ol>
<h2 id="heading-verify-the-application-is-running">Verify the Application is Running</h2>
<p>Once the application starts, you should see console output similar to this:</p>
<pre><code class="lang-kotlin">Tomcat started on port(s): <span class="hljs-number">8080</span> (http)
Started TodoApplicationKt <span class="hljs-keyword">in</span> X.XXX seconds
</code></pre>
<h3 id="heading-quick-health-check">Quick Health Check</h3>
<p>Open your browser and navigate to:</p>
<ul>
<li><p><strong>API Base URL:</strong> <code>http://localhost:8080/</code></p>
</li>
<li><p><strong>H2 Console:</strong> <code>http://localhost:8080/h2</code></p>
</li>
</ul>
<p>If you see a JSON response (likely an empty array <code>[]</code>) at the base URL, your application is running successfully!</p>
<h2 id="heading-accessing-the-h2-database-console">Accessing the H2 Database Console</h2>
<ol>
<li><p>Open your browser and go to <code>http://localhost:8080/h2</code></p>
</li>
<li><p>Enter the following connection details:</p>
<ul>
<li><p><strong>JDBC URL:</strong> <code>jdbc:h2:file:./data/todo-db</code></p>
</li>
<li><p><strong>User Name:</strong> (leave empty)</p>
</li>
<li><p><strong>Password:</strong> (leave empty)</p>
</li>
</ul>
</li>
<li><p>Click <strong>Connect</strong></p>
</li>
</ol>
<p>You can now run SQL queries directly to view your <code>TO_DO</code> table and inspect the data.</p>
<h2 id="heading-stopping-the-application">Stopping the Application</h2>
<ul>
<li><p><strong>In IDE:</strong> Click the red <strong>Stop</strong> button **** in the Run panel</p>
</li>
<li><p><strong>In Terminal:</strong> Press <code>Ctrl + C</code></p>
</li>
</ul>
<h1 id="heading-testing-your-api">Testing Your API</h1>
<p>Now that your application is running, let's test all the endpoints using Postman or Bruno. Make sure your server is running on <code>http://localhost:8080</code>.</p>
<h2 id="heading-1-create-a-todo-post">1. Create a Todo (POST)</h2>
<p><strong>Endpoint:</strong> <code>POST http://localhost:8080/</code></p>
<p><img src="https://raw.githubusercontent.com/ls-prabhu/taskbackend/refs/heads/master/images/postreq.png" alt="[]" /></p>
<h2 id="heading-2-get-all-todos-get">2. Get All Todos (GET)</h2>
<p><strong>Endpoint:</strong> <code>GET http://localhost:8080/</code></p>
<p><strong>Expected Response (200 OK):</strong></p>
<p><img src="https://raw.githubusercontent.com/ls-prabhu/taskbackend/refs/heads/master/images/getreq.png" alt="[]" /></p>
<h2 id="heading-3-get-todos-sorted-by-date-and-time-get">3. Get Todos Sorted by Date and Time (GET)</h2>
<p><strong>Endpoint:</strong> <code>GET http://localhost:8080/bytime</code></p>
<p>Returns all todos ordered by date and time (earliest first).</p>
<h2 id="heading-4-get-pending-todos-get">4. Get Pending Todos (GET)</h2>
<p><strong>Endpoint:</strong> <code>GET http://localhost:8080/pending</code></p>
<p>Returns only incomplete todos (<code>completed: false</code>).</p>
<h2 id="heading-5-get-completed-todos-get">5. Get Completed Todos (GET)</h2>
<p><strong>Endpoint:</strong> <code>GET http://localhost:8080/completed</code></p>
<p>Returns only completed todos (<code>completed: true</code>).</p>
<h2 id="heading-6-update-a-todo-patch">6. Update a Todo (PATCH)</h2>
<p><strong>Endpoint:</strong> <code>PATCH http://localhost:8080/1</code></p>
<p><strong>Request Body (JSON):</strong> <em>(Include only fields you want to update)</em></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"completed"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Completed Spring Boot Tutorial"</span>
}
</code></pre>
<p><strong>Expected Response (200 OK):</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"id"</span>: <span class="hljs-number">1</span>,
  <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Completed Spring Boot Tutorial"</span>,
  <span class="hljs-attr">"date"</span>: <span class="hljs-string">"2025-12-01"</span>,
  <span class="hljs-attr">"time"</span>: <span class="hljs-string">"14:30"</span>,
  <span class="hljs-attr">"completed"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"completedDate"</span>: <span class="hljs-string">"2025-11-30"</span>
}
</code></pre>
<h2 id="heading-7-delete-a-single-todo-delete">7. Delete a Single Todo (DELETE)</h2>
<p><strong>Endpoint:</strong> <code>DELETE http://localhost:8080/1</code></p>
<p><strong>Expected Response:</strong> <code>200 OK</code> (no content)</p>
<h2 id="heading-8-bulk-delete-todos-delete">8. Bulk Delete Todos (DELETE)</h2>
<p><strong>Endpoint:</strong> <code>DELETE http://localhost:8080/bulkdelete</code></p>
<p><strong>Request Body (JSON):</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"ids"</span>: [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>]
}
</code></pre>
<p><strong>Expected Response:</strong></p>
<pre><code class="lang-json"><span class="hljs-string">"3 deleted"</span>
</code></pre>
<h2 id="heading-9-delete-all-todos-delete">9. Delete All Todos (DELETE)</h2>
<p><strong>Endpoint:</strong> <code>DELETE http://localhost:8080/deleteall</code></p>
<p><strong>Expected Response:</strong></p>
<pre><code class="lang-json"><span class="hljs-string">"5 todos deleted"</span>
</code></pre>
<h2 id="heading-testing-tips">Testing Tips</h2>
<ol>
<li><p><strong>Test in Order:</strong> Start with POST to create data, then test GET, PATCH, and DELETE operations</p>
</li>
<li><p><strong>Check H2 Console:</strong> After each operation, verify changes in the database console (<code>http://localhost:8080/h2</code>)</p>
</li>
<li><p><strong>View Logs:</strong> Watch the console for SQL queries if <code>spring.jpa.show-sql=true</code> is enabled</p>
</li>
<li><p><strong>Use Collections:</strong> In Postman/Bruno, organize these requests in a collection for easy testing</p>
</li>
</ol>
<h1 id="heading-next-steps-amp-improvements">Next Steps &amp; Improvements</h1>
<p>Congratulations on building your first Spring Boot REST API! Here are ways to take it further:</p>
<ul>
<li><p><strong>Input Validation</strong> — Add <code>@NotBlank</code>, <code>@Size</code>, and <code>@Valid</code> annotations to ensure data integrity</p>
</li>
<li><p><strong>Error Handling</strong> — Implement <code>@ControllerAdvice</code> for global exception handling with proper HTTP status codes</p>
</li>
<li><p><strong>Production Database</strong> — Migrate from H2 to PostgreSQL, MySQL or MongoDB for production use</p>
</li>
<li><p><strong>User Management</strong> — Add User entity with relationships to todos for multi-user support</p>
</li>
<li><p><strong>Security</strong> — Add Spring Security with JWT authentication and role-based access control</p>
</li>
<li><p><strong>API Documentation</strong> — Integrate Swagger/OpenAPI for interactive API documentation</p>
</li>
</ul>
<h1 id="heading-conclusion">Conclusion</h1>
<p>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.</p>
<p>thanks for reading, if you have any suggestion don’t hesitate to <a target="_blank" href="https://beacons.ai/prabhuls">contact me</a>.</p>
]]></content:encoded></item></channel></rss>