#0 Uncommitted Begins.
Welcome to Uncommitted.DEV — 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.
Aleksi Kesälä’s devlog. A raw blog about building real software.
A web app for tracking and visualizing working hours or something like that.
- Built with the MERN stack.
Welcome to Uncommitted.DEV — 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.
Here’s what got done before the blog kicked off:
ObjectId
and Date
coercion —
enforces consistency across schema validations.
(Might get back to this later.)
Key Observations
• Avoid use ofas
andany
— sticking to type safety.
• Controllers hold the logic flow — services stay dumb.
• MongoDBObjectId
handled explicitly to keep schema clean.
• Pagination + filtering is typesafe and extensible.
No-code Thursday — just one fancy Kanban board and a lot of brainstorming around the project.
Breaking down the backend into focused Kanban boards was today´s agenda, it should guide the implementation going forward, at least until I derail. Here’s a little sneak peek:
Let’s not forget about the documentation.
Key Observations
• Writing things down helps keep chaos and scope creep off the charts.
• Kanban boards are solid for visualizing structure — some even give you an infinite canvas.
• I’m probably not sticking to this plan. Iterations will happen.
I’m lazy. I’m a programmer. So I automate.
A quick rundown of three DevEx upgrades I’ve added to this project: Docker, Yarn, and Husky. 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 rage-pushing broken commits.
Docker. I containerized the backend so I can spin
it up the same way anywhere — no “works on my machine” nonsense.
Basic setup: Dockerfile
for the app,
docker-compose.yml
for dev setup with Mongo.
Yarn. Switched to yarn
for
consistency across machines and CI. Faster installs, zero excuses,
and it’s been smooth so far. Locked down with
.yarnrc.yml
and yarn.lock
in git.
Husky. Git hooks for grown-ups. Pre-commit runs
prettier
and eslint
on changed files. If
something fails, the commit dies — good.
Why?
• I’ll forget. Git won’t.
• Automate boring stuff. Save brain cycles for real problems.
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
fakeAuth
middleware that injects an
accountId
onto req
.
Refactoring: Spaghettimonster tore through the code; after resting my brain, I untangled the mess and moved on to the next side quest.
RESTful Principles: Side quest: I simplified my
routes to GET /
, POST /
and
DELETE /:id
, making the API self-documenting.
// middleware/fakeAuth.ts
export const fakeAuth = (req, _res, next) => {
req.accountId = req.headers["x-account-id"] || "{fakeAccountId-here}";
next();
};
Implementing fakeAuth: This tiny middleware
injects an accountId
onto req
. Simply
mount it early:
app.use(fakeAuth);
Pro tip: For any future auth implementation,
mount fakeAuth
as early as possible to prototype
endpoints ASAP.
Key Observations
• Refactor your spaghetti, before it’s too late.
• RESTful routes make client integration straightforward.
•fakeAuth
injectsaccountId
ontoreq
, soGET /
can fetch only that user’s entries—no need for an/accountId
path param.
•fakeAuth
speeds up prototyping.
• Swapping in real OAuth later will be trivial once hooks are in place.
Basic CRUDs are in and working. Felt good to slow down and actually read the Mongoose docs for once — weirdly refreshing.
Now it’s time to shape real business logic: tighten the scopes, reinforce integrity, and clean up architectural leftovers from the speed-run phase.
Key Observations
• Two weeks with Copilot and already forgetting how to work without it.
• Might be a good time to write more — without Copilot.
• Back on Arch and Neovim. The basics. The simplicity. No distractions.
“Why define your data shape twice? With Zod, I define the schema once, and infer the TypeScript type straight from it.”
My favorite productivity hack in this project: the schema is the type. One definition, shared everywhere.
No copy-paste. No out-of-date docs. The compiler and Zod keep me honest.
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<typeof endTimeEntrySchema>;
Change the schema, and the type updates automatically. That's one source of truth for both validation and type safety.
dateString
and objectId
are reusable
Zod helpers for common patterns—so every date or MongoDB
ObjectId in my API follows the same rules, everywhere.
The schema is the contract. The type is inferred. Validation is the documentation. No room for guessing.