<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Uncommitted.DEV</title>
  <subtitle>A raw devlog by Aleksi Kesälä — building software in public.</subtitle>
  <link href="https://uncommitted.dev/rss.xml" rel="self" />
  <link href="https://uncommitted.dev/" />
  <updated>2025-06-29T05:57:56.703Z</updated>
  <id>https://uncommitted.dev/</id>
  <author>
    <name>Aleksi Kesälä</name>
  </author>
  <entry>
    <title>#0 Uncommitted Begins.</title>
    <link href="https://uncommitted.dev/#uncommitted-begins" />
    <id>https://uncommitted.dev/#uncommitted-begins</id>
    <updated>2025-05-19T00:00:00.000Z</updated>
    <content type="html"><![CDATA[<article
            class="post"
            id="uncommitted-begins"
            aria-labelledby="title-uncommitted-begins"
          >
            <header class="post-header">
              <time datetime="2025-05-19">2025-05-19</time>
              <h3 id="title-uncommitted-begins">#0 Uncommitted Begins.</h3>
            </header>
            <p>
              Welcome to <strong>Uncommitted.DEV</strong> — my personal devlog.
              I’ll be documenting the real work: building software, making
              decisions, and probably ranting about frameworks. This blog runs
              on raw HTML/CSS, zero frameworks, no bloat.
            </p>
          </article>]]></content>
  </entry>
  <entry>
    <title>#1 Before the Blog: A Dev Recap</title>
    <link href="https://uncommitted.dev/#dev-recap" />
    <id>https://uncommitted.dev/#dev-recap</id>
    <updated>2025-05-20T00:00:00.000Z</updated>
    <content type="html"><![CDATA[<article
            class="post"
            id="dev-recap"
            data-tag="backend"
            aria-labelledby="title-dev-recap"
          >
            <header class="post-header">
              <time datetime="2025-05-20">2025-05-20</time>
              <h3 id="title-dev-recap">
                #1 Before the Blog: A Dev Recap <span class="tag">Backend</span>
              </h3>
            </header>
            <p>Here’s what got done before the blog kicked off:</p>
            <section>
              <ul>
                <li>
                  Project initialization with <strong>TypeScript</strong>,
                  <strong>Express</strong>, and package management with
                  <strong>Yarn</strong>.
                </li>
                <li>
                  Setting up <strong>MongoDB</strong> Atlas for cloud database &
                  <strong>Mongoose</strong> for ODM. MongoDB model definitions.
                </li>
                <li>
                  <strong>Zod</strong> validators implemented for timeEntry CRUD
                  operations.
                </li>
                <li>
                  Custom <strong>ZodHelper</strong> for
                  <code>ObjectId</code> and <code>Date</code> coercion —
                  enforces consistency across schema validations.
                  <span class="dev-note">(Might get back to this later.)</span>
                </li>
              </ul>
            </section>

            <blockquote>
              <strong>Key Observations</strong><br />
              • Avoid use of <code>as</code> and <code>any</code> — sticking to
              type safety.<br />
              • Controllers hold the logic flow — services stay dumb.<br />
              • MongoDB <code>ObjectId</code> handled explicitly to keep schema
              clean.<br />
              • Pagination + filtering is typesafe and extensible.
            </blockquote>
          </article>]]></content>
  </entry>
  <entry>
    <title>#2 Setting Up Kanban</title>
    <link href="https://uncommitted.dev/#kanban-setup" />
    <id>https://uncommitted.dev/#kanban-setup</id>
    <updated>2025-05-22T00:00:00.000Z</updated>
    <content type="html"><![CDATA[<article
            class="post"
            id="kanban-setup"
            data-tag="project-management"
            aria-labelledby="title-kanban-setup"
          >
            <header class="post-header">
              <time datetime="2025-05-22">2025-05-22</time>
              <h3 id="title-kanban-setup">
                #2 Setting Up Kanban <span class="tag">Project Management</span>
              </h3>
            </header>
            <p>
              No-code Thursday — just one fancy Kanban board and a lot of
              brainstorming around the project.
            </p>
            <p>
              Breaking down the backend into focused
              <strong>Kanban boards</strong> was today´s agenda, it should guide
              the implementation going forward, at least until I derail. Here’s
              a little sneak peek:
            </p>
            <ul>
              <li>
                <strong>Auth</strong> - OAuth 2.0, JWT sessions & auth
                middleware
              </li>
              <li>
                <strong>Time Entries</strong> - CRUD operations, filtering, and
                pagination
              </li>
              <li><strong>Client / Project</strong> - Schemas and full CRUD</li>
              <li>
                <strong>Hourly Rates</strong> - Default & per-project overrides,
                auto earnings calculation(?)
              </li>
              <li>
                <strong>Dashboard</strong> - Endpoints for hours, earnings, and
                chart data
              </li>
              <li>
                <strong>Utils</strong> - Zod helpers, error handling middleware
              </li>
              <li>
                <strong>Extra</strong> - Easter eggs, CSV export, maybe
                websockets?
              </li>
            </ul>
            <p>Let’s not forget about the <strong>documentation</strong>.</p>
            <blockquote>
              <strong>Key Observations</strong><br />
              • Writing things down helps keep chaos and scope creep off the
              charts.<br />
              • Kanban boards are solid for visualizing structure — some even
              give you an infinite canvas.<br />
              • I’m probably not sticking to this plan. Iterations will happen.
            </blockquote>
          </article>]]></content>
  </entry>
  <entry>
    <title>#3 Automate or Suffer: My git commits are now smarter than me</title>
    <link href="https://uncommitted.dev/#devex-upgrades" />
    <id>https://uncommitted.dev/#devex-upgrades</id>
    <updated>2025-06-06T00:00:00.000Z</updated>
    <content type="html"><![CDATA[<article
            class="post"
            id="devex-upgrades"
            data-tag="developer-experience"
            aria-labelledby="title-devex-upgrades"
          >
            <header class="post-header">
              <time datetime="2025-06-06">2025-06-06</time>
              <h3 id="title-devex-upgrades">
                #3 Automate or Suffer: My git commits are now smarter than me
                <span class="tag">DevEx</span>
              </h3>
            </header>

            <p>I’m lazy. I’m a programmer. So I automate.</p>

            <p>
              A quick rundown of three DevEx upgrades I’ve added to this
              project: <strong>Docker</strong>, <strong>Yarn</strong>, and
              <strong>Husky</strong>. Docker's familiar, but I'm experimenting
              with Yarn for the first time here, and Husky is completely new to
              me. Early days, but the goal’s the same: automate the boring
              stuff, avoid mistakes, save time, and stop future me from
              <strong>rage-pushing</strong> broken commits.
            </p>

            <p>
              <strong>Docker.</strong> I containerized the backend so I can spin
              it up the same way anywhere — no “works on my machine” nonsense.
              Basic setup: <code>Dockerfile</code> for the app,
              <code>docker-compose.yml</code> for dev setup with Mongo.
            </p>

            <p>
              <strong>Yarn.</strong> Switched to <code>yarn</code> for
              consistency across machines and CI. Faster installs, zero excuses,
              and it’s been smooth so far. Locked down with
              <code>.yarnrc.yml</code> and <code>yarn.lock</code> in git.
            </p>

            <p>
              <strong>Husky.</strong> Git hooks for grown-ups. Pre-commit runs
              <code>prettier</code> and <code>eslint</code> on changed files. If
              something fails, the commit dies — good.
            </p>

            <blockquote>
              <strong>Why?</strong><br />
              • I’ll forget. Git won’t.<br />
              • Automate boring stuff. Save brain cycles for real problems.
            </blockquote>
          </article>]]></content>
  </entry>
  <entry>
    <title>#4 Long time no see, Backend!</title>
    <link href="https://uncommitted.dev/#backend-fakeauth" />
    <id>https://uncommitted.dev/#backend-fakeauth</id>
    <updated>2025-06-10T00:00:00.000Z</updated>
    <content type="html"><![CDATA[<article
            class="post"
            id="backend-fakeauth"
            data-tag="backend"
            aria-labelledby="title-backend-fakeauth"
          >
            <header class="post-header">
              <time datetime="2025-06-10">2025-06-10</time>
              <h3 id="title-backend-fakeauth">
                #4 Long time no see, Backend!
                <span class="tag">Backend</span>
              </h3>
            </header>

            <!-- Post content starts here -->
            <p>
              After a brief detour into DevEx tooling, I jumped back into the
              backend code and found my routes and handlers in disarray. To keep
              my sanity, I stripped out dead code, combined similar
              functionality, and reinforced RESTful naming conventions. Since
              I’m not ready to wrestle with OAuth, I threw together a simple
              <code>fakeAuth</code> middleware that injects an
              <code>accountId</code> onto <code>req</code>.
            </p>

            <p>
              <strong>Refactoring:</strong> Spaghettimonster tore through the
              code; after resting my brain, I untangled the mess and moved on to
              the next side quest.
            </p>

            <p>
              <strong>RESTful Principles:</strong> Side quest: I simplified my
              routes to <code>GET /</code>, <code>POST /</code> and
              <code>DELETE /:id</code>, making the API self-documenting.
            </p>

            <pre><code>
// middleware/fakeAuth.ts
export const fakeAuth = (req, _res, next) => {
  req.accountId = req.headers["x-account-id"] || "{fakeAccountId-here}";
  next();
};
  </code></pre>

            <p>
              <strong>Implementing fakeAuth:</strong> This tiny middleware
              injects an <code>accountId</code> onto <code>req</code>. Simply
              mount it early:
              <code>app.use(fakeAuth);</code>
            </p>
            <p>
              <strong>Pro tip:</strong> For any future auth implementation,
              mount <code>fakeAuth</code> as early as possible to prototype
              endpoints ASAP.
            </p>

            <blockquote>
              <strong>Key Observations</strong><br />
              • Refactor your spaghetti, before it’s too late.<br />
              • RESTful routes make client integration straightforward.<br />
              • <code>fakeAuth</code> injects <code>accountId</code> onto
              <code>req</code>, so <code>GET /</code> can fetch only that user’s
              entries—no need for an <code>/accountId</code> path param.<br />
              • <code>fakeAuth</code> speeds up prototyping.<br />
              • Swapping in real OAuth later will be trivial once hooks are in
              place.
            </blockquote>
            <!-- Post content ends here -->
          </article>]]></content>
  </entry>
  <entry>
    <title>#5 CRUDs are in — now what?</title>
    <link href="https://uncommitted.dev/#backend-crud-wrapup" />
    <id>https://uncommitted.dev/#backend-crud-wrapup</id>
    <updated>2025-06-15T00:00:00.000Z</updated>
    <content type="html"><![CDATA[<article
            class="post"
            id="backend-crud-wrapup"
            data-tag="backend"
            aria-labelledby="title-backend-crud-wrapup"
          >
            <header class="post-header">
              <time datetime="2025-06-15">2025-06-15</time>
              <h3 id="title-backend-crud-wrapup">
                #5 CRUDs are in — now what?
                <span class="tag">Backend</span>
              </h3>
            </header>

            <!-- Post content starts here -->
            <p>
              Basic CRUDs are in and working. Felt good to slow down and
              actually read the Mongoose docs for once — weirdly refreshing.
            </p>

            <section>
              <h4>Next steps</h4>
              <p>
                Now it’s time to shape real business logic: tighten the scopes,
                reinforce integrity, and clean up architectural leftovers from
                the speed-run phase.
              </p>
            </section>

            <ul>
              <li>CRUD routes done</li>
              <li>Business logic incoming</li>
              <li>Frontend knocking on the door</li>
            </ul>

            <blockquote>
              <strong>Key Observations</strong><br />
              • Two weeks with Copilot and already forgetting how to work
              without it.<br />
              • Might be a good time to write more — without Copilot.<br />
              • Back on Arch and Neovim. The basics. The simplicity. No
              distractions.
            </blockquote>
            <!-- Post content ends here -->
          </article>]]></content>
  </entry>
  <entry>
    <title>#6 Validation as the Source of Truth</title>
    <link href="https://uncommitted.dev/#dev-shared-types-zod" />
    <id>https://uncommitted.dev/#dev-shared-types-zod</id>
    <updated>2025-06-29T00:00:00.000Z</updated>
    <content type="html"><![CDATA[<article
            class="post"
            id="dev-shared-types-zod"
            data-tag="architecture"
            aria-labelledby="title-dev-shared-types-zod"
          >
            <header class="post-header">
              <time datetime="2025-06-29">2025-06-29</time>
              <h3 id="title-dev-shared-types-zod">
                #6 Validation as the Source of Truth
                <span class="tag">architecture</span>
              </h3>
            </header>

            <!-- Post content starts here -->
            <p>
              <strong
                >“Why define your data shape twice? With Zod, I define the
                schema once, and infer the TypeScript type straight from
                it.”</strong
              >
            </p>

            <p>
              My favorite productivity hack in this project: the schema
              <strong>is</strong> the type. One definition, shared everywhere.
            </p>

            <ul>
              <li>
                Validate incoming data at runtime with a single source of truth.
              </li>
              <li>
                Use the same schemas for backend API validation and frontend
                type safety.
              </li>
              <li>
                If I change a field, my editor instantly slaps out what needs
                fixing — no surprises.
              </li>
            </ul>

            <p>
              No copy-paste. No out-of-date docs. The compiler and Zod keep me
              honest.
            </p>

            <section>
              <h4>How it works in practice</h4>
              <pre><code>import { dateString, objectId } from "@/../zodHelper";
import { z } from "zod";

const endTimeEntrySchema = z.object({
  params: z.object({
    id: objectId,
  }),
  body: z.object({
    endedAt: dateString,
  }),
});

// Type is always up-to-date with the schema:
export type EndTimeEntryInput = z.infer&lt;typeof endTimeEntrySchema&gt;;
</code></pre>
              <p>
                Change the schema, and the type updates automatically. That's
                one source of truth for both validation and type safety.
              </p>
            </section>

            <section>
              <h4>Bonus: Helper Schemas</h4>
              <p>
                <code>dateString</code> and <code>objectId</code> are reusable
                Zod helpers for common patterns—so every date or MongoDB
                ObjectId in my API follows the same rules, everywhere.
              </p>
            </section>

            <blockquote>
              <strong>
                The schema is the contract. The type is inferred. Validation is
                the documentation. No room for guessing.
              </strong>
            </blockquote>
            <!-- Post content ends here -->
          </article>]]></content>
  </entry>
</feed>
