<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Harsh Kochar</title>
    <description>The latest articles on DEV Community by Harsh Kochar (@hk_build).</description>
    <link>https://dev.to/hk_build</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2737021%2Fb86726fc-d245-470d-a38f-dd9007d94b90.png</url>
      <title>DEV Community: Harsh Kochar</title>
      <link>https://dev.to/hk_build</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hk_build"/>
    <language>en</language>
    <item>
      <title>PaperTrail: I built my friend a paperwork checklist where the AI has to show its receipts</title>
      <dc:creator>Harsh Kochar</dc:creator>
      <pubDate>Mon, 05 Oct 2026 07:04:52 +0000</pubDate>
      <link>https://dev.to/hk_build/papertrail-i-built-my-friend-a-paperwork-checklist-where-the-ai-has-to-show-its-receipts-lkf</link>
      <guid>https://dev.to/hk_build/papertrail-i-built-my-friend-a-paperwork-checklist-where-the-ai-has-to-show-its-receipts-lkf</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PaperTrail&lt;/strong&gt; turns a pile of application paperwork into a checklist where every tick comes with a receipt.&lt;/p&gt;

&lt;p&gt;I built it for [FRIEND'S NAME], who is applying to [universities / scholarships] this year. [In one or two sentences, your own words: what you watched them struggle with.]&lt;/p&gt;

&lt;p&gt;No single document is the hard part. The hard part is knowing at any moment which requirements are covered, which are missing, and which documents will expire before the deadline.&lt;/p&gt;

&lt;p&gt;PaperTrail does five things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reads the requirements.&lt;/strong&gt; Upload the program's requirements as a PDF or paste them as text. PaperTrail extracts each requirement and its deadline, and links each one to the sentence it came from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reads your documents.&lt;/strong&gt; Upload a transcript, a test score report or a CV. Each requirement is either matched to a passage that satisfies it or marked &lt;em&gt;missing&lt;/em&gt;, &lt;em&gt;expired&lt;/em&gt; or &lt;em&gt;needs review&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracks what's left.&lt;/strong&gt; Open items become tasks, with reminders that survive restarts. When you mark a task done, its pending reminder is cancelled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answers questions with citations.&lt;/strong&gt; Ask "Why is my English test marked for review?" and the answer quotes the score report and links to the page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exports a report&lt;/strong&gt; that separates what the AI found from what you verified yourself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rule I held myself to: &lt;strong&gt;the AI proposes, the person decides.&lt;/strong&gt; The UI shows "Evidence found (AI)" and "Verified by you" as different states. Nothing counts as verified until a human says so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create a workspace with a 2027-01-15 deadline and upload the program's requirements PDF.&lt;/li&gt;
&lt;li&gt;Review the extracted requirements. Each one links to its source sentence.&lt;/li&gt;
&lt;li&gt;Upload a transcript and an English test score report. The transcript is matched with a cited passage. The English test goes to &lt;em&gt;needs review&lt;/em&gt; because it expires on 2026-12-12, before the deadline.&lt;/li&gt;
&lt;li&gt;Inject a failure while a document is processing. Attempt 1 fails and shows up in Sentry. Temporal retries, and attempt 2 succeeds.&lt;/li&gt;
&lt;li&gt;Set a reminder, complete the task, and watch the reminder get suppressed instead of sent.&lt;/li&gt;
&lt;li&gt;Ask the assistant why the English test needs review.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HarshKochar9008" rel="noopener noreferrer"&gt;
        HarshKochar9008
      &lt;/a&gt; / &lt;a href="https://github.com/HarshKochar9008/ockto" rel="noopener noreferrer"&gt;
        ockto
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;PaperTrail&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Every document. Every requirement. One clear path forward.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;PaperTrail turns a pile of application paperwork into an evidence-backed checklist. Upload the requirements (a PDF or pasted text) and your supporting documents. PaperTrail extracts the requirements, matches each one to passages in your documents, flags what's missing, expired or uncertain, tracks deadlines, and sends reminders that survive restarts. Every AI assessment links to the page and passage it relied on, and nothing counts as verified until you say so.&lt;/p&gt;
&lt;p&gt;The MVP targets university and scholarship applications. Nothing in the data model is specific to them.&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;browser ── React + TanStack Query ──▶ Fastify API (/api/v1) ──▶ Tiger Data / Postgres + pgvector
                                          │                         ▲
                                          └─ start / signal ─▶ Temporal ─▶ Worker (activities)
                                                                              ├─ PDF parser / OCR
                                                                              ├─ Gemma via Ollama (or any OpenAI-compatible endpoint)
                                                                              └─ webhook reminders
API + worker ── OTLP traces + errors ──▶ Sentry
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HarshKochar9008/ockto" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The open-weight core is &lt;strong&gt;Gemma&lt;/strong&gt;, running locally through &lt;strong&gt;Ollama&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gemma3:4b&lt;/code&gt; extracts requirements, classifies documents, assesses evidence, answers questions and reads text from images.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;embeddinggemma&lt;/code&gt; produces 768-dimensional embeddings for search.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server reaches Ollama through an OpenAI-compatible adapter. Pointing the same code at a hosted Gemma endpoint means changing one environment variable.&lt;/p&gt;

&lt;p&gt;Here's how the pieces connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;browser ── React + TanStack Query ──▶ Fastify API ──▶ Postgres + pgvector (Tiger Data)
                                          │
                                          └─▶ Temporal ─▶ Worker
                                                            ├─ PDF parser / OCR
                                                            ├─ Gemma via Ollama
                                                            └─ webhook reminders
API + worker ── traces + errors ──▶ Sentry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Temporal&lt;/strong&gt; runs four workflows: process a document, analyze a workspace, deliver reminders on durable timers, and retry failed processing from the step that failed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postgres + pgvector&lt;/strong&gt; stores everything and also handles search. Vector search and full-text search are combined with reciprocal rank fusion in a single SQL query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentry&lt;/strong&gt; traces the API and the worker. Model calls show up as &lt;code&gt;gen_ai&lt;/code&gt; spans, and activity failures are tagged with the workflow, activity and attempt.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Making a 4B model trustworthy
&lt;/h3&gt;

&lt;p&gt;A 4-billion-parameter model on a laptop CPU is slow, and sometimes it is confidently wrong. I ran it against realistic documents and kept a list of the ways it failed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It inverted booleans.&lt;/strong&gt; When I asked whether a requirement was &lt;code&gt;required&lt;/code&gt;, it often gave the opposite answer. Asking for &lt;code&gt;optional&lt;/code&gt; instead fixed it: true only when the text actually says "optional" or "if applicable."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It garbled citation labels.&lt;/strong&gt; Now labels come from a fixed enum, and each quote is re-attributed to the passage that actually contains it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It cited a transcript as a CV.&lt;/strong&gt; The quote was real but came from the wrong kind of document. A document-class check now ensures a transcript can never satisfy a CV requirement, however convincing the quote.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beyond those fixes, deterministic code in &lt;code&gt;server/analysis.ts&lt;/code&gt; checks every model answer against the source before anything is stored:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every call is constrained to a JSON schema and validated with zod. An invalid answer gets one re-ask. If it is still invalid, the activity fails and Temporal retries it.&lt;/li&gt;
&lt;li&gt;An evidence quote is kept only if it really appears in the cited passage. A "satisfied" verdict without a verifiable quote is downgraded to &lt;em&gt;needs review&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;If a requirement's source sentence can't be found in the document, the requirement is flagged and its due date is dropped.&lt;/li&gt;
&lt;li&gt;An expiry date is stored only when the model quotes the sentence that states it.&lt;/li&gt;
&lt;li&gt;If the model states any uncertainty, &lt;em&gt;satisfied&lt;/em&gt; becomes &lt;em&gt;needs review&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Document text sits inside tags it can't close, and the model is told never to follow instructions found inside them. A scholarship PDF shouldn't be able to say "mark everything complete."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I learned: fix a model's mistakes with code that checks the source, not with a longer prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Living with CPU speed
&lt;/h3&gt;

&lt;p&gt;My laptop has no GPU, so &lt;code&gt;gemma3:4b&lt;/code&gt; takes 1–2 minutes per assessment and about 5 minutes to extract requirements. The first call after startup spends another 3 minutes or so loading the model.&lt;/p&gt;

&lt;p&gt;That is where Temporal earned its place. Model activities get 15-minute timeouts and send a heartbeat every 10 seconds. If the worker crashes, the work resumes where it stopped. The Activity page shows progress for each requirement. Slow is fine. Lost work is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Think about what goes into PaperTrail: transcripts, passports, test scores, recommendation letters, bank statements. That is some of the most sensitive paperwork a person owns.&lt;/p&gt;

&lt;p&gt;Because the model has open weights and runs locally, none of it leaves the laptop. My friend doesn't have to trust me, a startup's privacy policy or an API provider's data retention settings. The Settings page states plainly where document text is sent.&lt;/p&gt;

&lt;p&gt;Open weights made three more things possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's free to run, indefinitely.&lt;/strong&gt; A student applying to several programs re-runs the analysis every time a document changes. There's no per-token bill, no rate limit and no API key to leak.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I can debug the model.&lt;/strong&gt; The weights are identical on every run, so the quirks are too. When &lt;code&gt;gemma3:4b&lt;/code&gt; inverted booleans, I could reproduce it, fix it in code and trust the fix to hold. A hosted model can change between one week and the next without warning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's easy to swap.&lt;/strong&gt; The adapter speaks the OpenAI-compatible protocol, so a GPU machine or a hosted Gemma endpoint is one environment variable away.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of the stack is open source as well: Temporal, Postgres, pgvector, Ollama, Fastify and React. Hosted services such as Tiger Cloud, Temporal Cloud and Sentry are optional. Locally, a single &lt;code&gt;docker compose up&lt;/code&gt; starts everything.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Meet Flywheel — I Turned My Daily Habit Into Fire You Can Actually Own</title>
      <dc:creator>Harsh Kochar</dc:creator>
      <pubDate>Mon, 13 Jul 2026 06:37:17 +0000</pubDate>
      <link>https://dev.to/hk_build/meet-flywheel-i-turned-my-daily-habit-into-fire-you-can-actually-own-4mb4</link>
      <guid>https://dev.to/hk_build/meet-flywheel-i-turned-my-daily-habit-into-fire-you-can-actually-own-4mb4</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for Weekend Challenge: Passion Edition&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Everyone says "consistency is everything," then loses the streak in a spreadsheet nobody opens again. A habit — a craft, a team, a side project — deserves proof that outlives the app you tracked it in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftlb8na9ev647cftl5d94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftlb8na9ev647cftl5d94.png" alt=" " width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flywheel&lt;/strong&gt; is a Solana devnet app that turns a daily streak into a badge you actually own. Declare something you refuse to miss a day of, check in, and when you cross a threshold, mint the streak as an on-chain trophy that lives in your wallet — not in my database.&lt;/p&gt;

&lt;p&gt;Here's the loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔥 &lt;strong&gt;Declare a habit&lt;/strong&gt; — name it, pick a category, pick an emblem&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Check in daily&lt;/strong&gt; — miss a day and the streak resets; the badges you already minted don't&lt;/li&gt;
&lt;li&gt;🏅 &lt;strong&gt;Four tiers to earn&lt;/strong&gt; — &lt;strong&gt;Spark&lt;/strong&gt; (7 days), &lt;strong&gt;Motion&lt;/strong&gt; (30), &lt;strong&gt;Momentum&lt;/strong&gt; (100), &lt;strong&gt;Unstoppable&lt;/strong&gt; (365)&lt;/li&gt;
&lt;li&gt;⛓️ &lt;strong&gt;Immortalize it&lt;/strong&gt; — one click mints a Metaplex Core NFT straight to your Phantom wallet, with a live Solana Explorer link&lt;/li&gt;
&lt;li&gt;🃏 &lt;strong&gt;Trophy Shelf&lt;/strong&gt; — a flip-card gallery that reads badges &lt;em&gt;from the chain&lt;/em&gt;, not local storage — clear your browser and they're still there&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything runs on Solana devnet with free airdropped SOL, on purpose — this is about proving the habit, not spending money on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Inspired This
&lt;/h2&gt;

&lt;p&gt;I have a graveyard of streaks that only ever lived in my own head: a run of gym mornings, a stretch of daily commits, a season of never missing a match. Every one of them evaporated the moment I stopped believing my own memory of them — no proof, no receipt, nothing to show for the discipline once the feeling faded.&lt;/p&gt;

&lt;p&gt;The "passion" theme made me want to build the opposite of a vanity metric. Not a streak counter that resets your pride to zero the day you forget to open the app, but something that keeps the momentum — the &lt;em&gt;flywheel&lt;/em&gt; — spinning even after you've moved on. If a habit is real, it should survive outside your own head, verifiable by anyone, on a ledger that doesn't care whether you're still paying attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🔗 Live → &lt;em&gt;[&lt;a href="https://flywheel0.vercel.app/" rel="noopener noreferrer"&gt;https://flywheel0.vercel.app/&lt;/a&gt;]&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tech&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;React 19 + TypeScript + Tailwind v4 + Vite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wallet&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@solana/wallet-adapter-react&lt;/code&gt; (Phantom, devnet)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mint&lt;/td&gt;
&lt;td&gt;Metaplex Umi + &lt;code&gt;mpl-core&lt;/code&gt; — one Core asset per badge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Art&lt;/td&gt;
&lt;td&gt;Badge SVGs generated per tier, uploaded via Irys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Streaks live in &lt;code&gt;localStorage&lt;/code&gt;; badges live on-chain&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The chain is the only source of truth that matters.&lt;/strong&gt; The Trophy Shelf never trusts local state — it queries devnet RPC for Core assets owned by the connected wallet. I could wipe every cookie on my machine and my Momentum badge would still be sitting in my wallet, because that's the entire point of the project.&lt;/p&gt;

&lt;p&gt;The part I'm proudest of is the failure handling. Metadata upload to Irys can time out — so after 60 seconds the mint falls back to static metadata served from the app itself, logs it loudly, and still lands on-chain. Faucet rate-limited mid-demo? Retry button, no crash. The ownership and the mint are never fake; only the JSON description occasionally takes the scenic route.&lt;/p&gt;

&lt;p&gt;There's also a hidden &lt;strong&gt;time machine&lt;/strong&gt; (&lt;code&gt;Ctrl+Shift+D&lt;/code&gt;) that fast-forwards the clock and simulates check-ins so a 365-day streak can be demoed live in ninety seconds — it can shift &lt;em&gt;time&lt;/em&gt;, but it can't fake a single thing on-chain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flj1xkhndvz9guakizsao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flj1xkhndvz9guakizsao.png" alt=" " width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It's Passion
&lt;/h2&gt;

&lt;p&gt;The theme was "passion," and the easiest interpretation is a fan page. I wanted proof instead of a page. A streak that only lives in an app's database is a vibe. A streak minted as a wallet-owned, Explorer-verifiable NFT is a receipt — and receipts are what separate "I say I'm passionate" from "here's 100 days, audit it yourself."&lt;/p&gt;

&lt;p&gt;My own longest streak while building this was the project itself: several days of shipping straight through, no missed check-ins, until the Unstoppable tier stopped being hypothetical.&lt;/p&gt;

&lt;p&gt;Built solo in a weekend. Momentum, proven daily.&lt;/p&gt;

&lt;p&gt;— Harsh Kochar (&lt;a href="mailto:harshkochar88@gmail.com"&gt;harshkochar88@gmail.com&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>weekendchallenge</category>
      <category>devchallenge</category>
      <category>solana</category>
      <category>web3</category>
    </item>
    <item>
      <title>Branch Your Database Like You Branch Your Code</title>
      <dc:creator>Harsh Kochar</dc:creator>
      <pubDate>Thu, 09 Jul 2026 05:25:51 +0000</pubDate>
      <link>https://dev.to/hk_build/branch-your-database-like-you-branch-your-code-5cd5</link>
      <guid>https://dev.to/hk_build/branch-your-database-like-you-branch-your-code-5cd5</guid>
      <description>&lt;h1&gt;
  
  
  Stop Testing Migrations in Production: Why Database Branching Changes Everything
&lt;/h1&gt;

&lt;p&gt;If you've ever run &lt;code&gt;git checkout -b feature/new-schema&lt;/code&gt; and felt a little too confident about it — only to realize your database doesn't work the same way your code does — you know the problem. Branching solved collaboration for code decades ago. Your database is still stuck in 2005.&lt;/p&gt;

&lt;p&gt;Neon fixes that. With &lt;strong&gt;database branching&lt;/strong&gt;, you can create a full, isolated copy of your Postgres database in seconds — not by cloning terabytes of data, but by taking a lightweight snapshot of the current state. It works the same way &lt;code&gt;git branch&lt;/code&gt; works for code: cheap, instant, and disposable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with "just use a staging database"
&lt;/h2&gt;

&lt;p&gt;Most teams handle this today with one of three approaches, and all of them hurt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A shared staging database.&lt;/strong&gt; Someone runs a migration, it breaks another engineer's feature branch, and now three people are debugging the same broken table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spinning up a fresh database per environment.&lt;/strong&gt; This means writing seed scripts, waiting for provisioning, and hoping your synthetic data resembles production closely enough to catch real bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing directly against production (or a stale nightly dump).&lt;/strong&gt; Fast, but terrifying — and stale data means your tests don't reflect what's actually happening in the app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these give you what you actually want: a real copy of your production data, isolated from everyone else, that you can throw away when you're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  How branching works under the hood
&lt;/h2&gt;

&lt;p&gt;Neon separates storage from compute. Your data lives in a multi-version, copy-on-write storage layer, not tied to a single running instance. When you create a branch, Neon doesn't copy your data — it creates a new pointer into that same storage layer at a specific point in time.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Branch creation takes seconds, regardless of database size.&lt;/strong&gt; A 5GB database and a 500GB database branch in roughly the same amount of time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branches are copy-on-write.&lt;/strong&gt; You only pay for the data that diverges from the parent branch, not a full duplicate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every branch is a fully functional Postgres database.&lt;/strong&gt; Same connection string format, same extensions, same behavior — nothing to relearn.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can create a branch from &lt;code&gt;main&lt;/code&gt;, from another branch, or from a specific point in time — which means you can also use branching to recover a table you accidentally dropped ten minutes ago, without restoring the entire database.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like in practice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Per-PR preview databases.&lt;/strong&gt; Wire branch creation into your CI pipeline so every pull request gets its own database branch, seeded with real (or anonymized) production-like data. Your preview deploy and your database preview deploy together. When the PR closes, delete the branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a branch for a new feature&lt;/span&gt;
neon branches create &lt;span class="nt"&gt;--project-id&lt;/span&gt; your-project-id &lt;span class="nt"&gt;--name&lt;/span&gt; feature/add-invoicing

&lt;span class="c"&gt;# Get the connection string and point your app at it&lt;/span&gt;
neon connection-string feature/add-invoicing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Safe migration testing.&lt;/strong&gt; Branch from &lt;code&gt;main&lt;/code&gt;, run your migration against the branch, verify it does what you expect, then run it against production with confidence. No more crossed fingers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instant rollback for experiments.&lt;/strong&gt; Try a risky schema change, a bulk data transformation, or a new indexing strategy on a branch. If it doesn't work, delete the branch — production was never touched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reproducing bugs with real data.&lt;/strong&gt; Branch from the exact point in time a bug was reported, and debug against the data as it actually existed — not a guess at what might have caused it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Branching is built into every Neon project — there's no separate feature to enable. From the console, the CLI, or the API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;neon branches create &lt;span class="nt"&gt;--project-id&lt;/span&gt; your-project-id &lt;span class="nt"&gt;--name&lt;/span&gt; my-branch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or straight from a point in time, if you need to look backward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;neon branches create &lt;span class="nt"&gt;--project-id&lt;/span&gt; your-project-id &lt;span class="nt"&gt;--timestamp&lt;/span&gt; &lt;span class="s2"&gt;"2026-07-01T12:00:00Z"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each branch gets its own compute endpoint and connection string, so it plugs into your existing workflow without any special handling in your application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger idea
&lt;/h2&gt;

&lt;p&gt;Branching isn't just a nice developer-experience feature bolted onto Postgres — it's what becomes possible once you stop treating storage and compute as one inseparable unit. When creating a database is as cheap and fast as creating a Git branch, it stops being a thing you plan around and starts being a thing you do constantly, without thinking twice.&lt;/p&gt;

&lt;p&gt;Give it a try on your next PR. It's a small habit change with an outsized effect on how confidently your team ships schema changes.&lt;/p&gt;

</description>
      <category>database</category>
      <category>devops</category>
      <category>postgres</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
