Philosophy
Dulak is the Banjar word for bored — and the name is the manifesto: code that is “boring” is the most valuable code there is. Boring here does not mean dull — it means predictable. No surprises, no clever tricks that require the next maintainer to reverse-engineer intent. Whoever comes next — human or AI agent — should be able to understand the code, change it, and not be afraid of breaking it.
Deliberately boring
Section titled “Deliberately boring”Every choice trades “clever” for “obviously right”. When two approaches exist, only the simpler one is kept. For example, route handlers are written inline in their route file instead of being split into abstract controllers. Boring? Yes. Followable at a glance? Far more.
Zero-dependency where it’s cheap
Section titled “Zero-dependency where it’s cheap”Every dependency is a liability: it must be upgraded, audited, and can
break under you. When 60 lines of our own code are enough, we write them.
In this repo: the rate limiter is hand-rolled (rate-limit.ts), the
Google OAuth client is plain fetch (no SDK), CSS is vanilla by default,
and the database layer is raw bun:sqlite prepared statements — no ORM.
ORMs were built to help humans avoid writing SQL; in 2026, AI generates
correct, optimized raw SQL on demand. The friction ORMs solved is gone —
what remains is the abstraction tax: a dependency to upgrade, a query
plan to debug, a layer between you and the database.
One runtime, wired end to end
Section titled “One runtime, wired end to end”Dulak is not a PHP/Laravel-style stack — no separate language runtime,
package manager, web server, and database to install and wire together
before your first php artisan serve. The whole stack is Bun:
Bun.serve is the HTTP server, bun:sqlite is the database, Bun.build
is the bundler, bun test is the test runner, bun install is the
package manager. Setup is exactly three steps — install Bun, scaffold
with bun create dulak, run bun run dev — and you have a running app
with auth, migrations, SSR, and tests. In dev, if port 4000 is busy, it
auto-increments to the next free port — no manual PORT juggling.
One obvious way to do things
Section titled “One obvious way to do things”Structure is standardized, on purpose: routes only live in
routes/<feature>.routes.ts, all SQL lives in db.ts, environment
variables are read only in config.ts. No “structural creativity” — that
is the point. When everyone writes the same way, anyone can find anything.
Discoverability as a contract
Section titled “Discoverability as a contract”Given a URL you can name the file that owns it: /login →
routes/auth.routes.ts, /uploads → routes/uploads.routes.ts. Every
URL lives in exactly one file, with its GET render and POST actions
together. Paste a broken URL and you land in exactly one place — no
guessing.
Production-grade guardrails from day one
Section titled “Production-grade guardrails from day one”The infrastructure a deployed app needs — CSRF, two-layer rate limiting (global DDoS baseline + auth brute-force), security headers, versioned migrations, graceful shutdown, Docker, CI — is wired and tested from day one, not scaffolded. What is missing is your business logic, and that is the point: you start from a skeleton that already works, not one you have to harden.
Boring versions, pinned, not chased
Section titled “Boring versions, pinned, not chased”A starter gets forked and maintained for years — its dependencies decide how much forced maintenance lands on the fork owner. “Current” does not mean “always the latest”: Dulak pins specific stable versions (Hono 4.x, Bun 1.4+, Inertia v3) and upgrades only when there is a concrete reason. A pinned version is a resting point, not a moving target that drags every fork into an upgrade cycle.
- No inherited rewrite churn. A dependency whose next major is a full rewrite strands the boilerplate on a legacy track with no upgrade path.
- Breaking changes become schedulable. On a stable track, upgrades are deliberate, documented migrations you can plan and test — not emergency fixes when a beta dependency shifts under you.
- A new major must prove itself. It ships when there is a concrete reason (security, ecosystem, maintenance), not because it is the newest thing.
Correctness over cleverness
Section titled “Correctness over cleverness”Synchronous, explicitly typed, parameterized queries; fail-fast
configuration; deterministic tests (bun test --isolate). Prefer the
boring implementation that is obviously correct over the clever one that
is hard to verify.
Built for AI agents
Section titled “Built for AI agents”The “next maintainer” includes the agent writing the next feature — which,
in this project, is the main way the code evolves. That is why conventions
are codified where agents read them (AGENTS.md), validation errors have
exact, documented shapes (TypeBox), mistakes fail at compile time
(strict + noUncheckedIndexedAccess), and the test suite runs
deterministically as the safety net. A codebase an agent can extend
without inventing conventions is a codebase that stays coherent.