The same minimal multi-user blog — auth, posts CRUD with pagination and ownership checks, comments, server-side validation, a deferred welcome notification, and integration tests — implemented six times from the same specification:
| Directory | Stack |
|---|---|
guren/ |
Guren — fullstack TypeScript framework on Bun (Hono + Drizzle + Inertia/React) |
hono/ |
Hono + Drizzle, hand-rolled session auth, Vite React SPA + JSON API |
nextjs/ |
Next.js App Router + Server Actions + Auth.js + Drizzle |
tanstack/ |
TanStack Start — file-based routes + server functions, Drizzle, cookie sessions |
adonisjs/ |
AdonisJS 7 — Inertia React starter kit, Lucid ORM, session auth, VineJS, Bouncer |
nestjs/ |
NestJS + Drizzle, Passport session auth, Vite React SPA + JSON API |
All implementations use TypeScript (strict), SQLite, and React, and all use Drizzle ORM except AdonisJS, which uses its first-party Lucid ORM (see SPEC.md) — so the differences you see are framework glue, not ORM or language differences.
| Metric | guren/ |
hono/ |
nextjs/ |
tanstack/ |
adonisjs/ |
nestjs/ |
|---|---|---|---|---|---|---|
| Source files | 38 | 24 | 23 | 24 | 68 | 45 |
| Source LOC | 1,052 | 945 | 864 | 1,037 | 2,438 | 1,370 |
| Handwritten LOC (vs scaffold) | 632 | 977 | 903 | 1,001 | 600 | 1,310 |
| Config LOC | 78 | 32 | 118 | 56 | 84 | 98 |
| Test files | 1 | 5 | 6 | 6 | 4 | 5 |
| Test LOC | 135 | 304 | 526 | 371 | 327 | 391 |
| Direct dependencies | 17 | 15 | 17 | 21 | 43 | 52 |
| Context tokens (cl100k) | 9,711 | 8,312 | 7,139 | 8,720 | 17,319 | 10,997 |
| External verification LOC | 0 | 0 | 0 | 0 | 0 | 0 |
| Agent guidance LOC | 1,784 | 0 | 5 | 0 | 0 | 0 |
| Agent guidance tokens (cl100k) | 22,582 | 0 | 79 | 0 | 0 | 0 |
See MEASUREMENT.md for exactly what is counted, what is excluded, and why. Reproduce with:
bun install
bun scripts/measure.tsHandwritten LOC is the headline. To take a fresh project to the full
spec, the two batteries-included frameworks are in a league of their own —
AdonisJS at ~600 typed lines and Guren at ~632 — versus ~903 on Next.js,
~977 on plain Hono, ~1,001 on TanStack Start, and ~1,310 on NestJS. The rest
of each tree was produced by generators whose pristine output is committed
under baselines/ so every diff is verifiable.
Raw Source LOC separates the batteries-included twins. Scaffolds generate real, owned code — and here Guren and AdonisJS diverge sharply: reaching the same spec leaves you maintaining 1,052 lines across 38 files on Guren versus 2,438 lines across 68 files on AdonisJS (and 43 direct dependencies versus Guren's 17). Similar typing effort, very different long-term surface. Whether generated-but-owned code is a cost is exactly what this repository lets you judge.
Where the lines go (Source LOC by area, three representative stacks).
Areas cover Source files only, so each column sums to that implementation's
Source LOC; nextjs/'s frontend is colocated with its routes under
src/app/, and guren/ has no Auth row because authentication is framework
code rather than app code:
| Area | guren/ |
hono/ |
nextjs/ |
|---|---|---|---|
| Frontend (React UI) | 466 | 536 | 476 |
| Routes / controllers / actions | 180 | 153 | 196 |
| DB + models | 104 | 79 | 107 |
| Auth | — | 61 | 38 |
| Validation + serialization + authz | 99 | 48 | 38 |
| Plumbing (bootstrap / providers) | 146 | 68 | 9 |
| Agent harness (hook) | 57 | — | — |
Two intuitions this corrects. "Surely plain Hono writes much more DB code" — no: all implementations share Drizzle, so the DB layer is ~79–107 lines everywhere; that difference was deliberately designed out. "Surely the SPA means far more frontend" — barely: the forms, lists, and error displays are the same spec everywhere, so UI code converges (~466–536 lines); what Inertia saves Hono's SPA is only the fetch-wrapper/router/auth-context plumbing. At this app size, what a framework saves you (auth logic, error conventions) and what its structure adds (providers, entries, layered files) nearly cancel out in totals — the frameworks' savings are concentrated in who wrote the lines (Handwritten LOC), and the hand-rolled share grows with every feature a framework would otherwise ship (mail verification, password reset, real queues), while the plumbing is a one-time fixed cost.
Test LOC shows API leverage. The same SPEC §6 scenarios take 135 lines
with Guren's TestApp fluent client, ~330–390 on AdonisJS (Japa), TanStack,
Hono, and NestJS, and 526 on Next.js (where integration-testing Server
Actions requires notable setup).
Context tokens measure the full-read cost for an AI agent — and reading
everything is how an agent must approach most of these apps (17.3k tokens for
AdonisJS, ~7–11k for the rest). Guren ships a cheaper path: bunx guren context emits a project map (routes, models, controllers, pages, and the
signatures of the framework APIs they use) that measures 1,407 tokens
against 9,711 for reading the tree — 7× cheaper. That map was 404 tokens
when this section was first written, before the output began carrying API
signatures; the figure here is guren context tokenised with cl100k on
@guren/cli 2.0.0, the version this implementation pins. bunx guren check
/ bunx guren audit verify route↔controller↔page consistency
and validation/auth coverage mechanically after an agent edits. AdonisJS
comes closest in spirit (node ace list:routes, generated type registries);
the other stacks have no equivalent — verification is reading the diff.
Read that alongside Agent guidance, which is the other half of the bill.
Context tokens measure the cost of reading the app; they deliberately exclude
the instruction files an agent's harness loads before it touches anything, and
guren/ ships by far the most of those — 1,784 lines and 22,582 cl100k tokens
of CLAUDE.md, rules, skills and agent definitions, against 79 tokens for
nextjs/ and none anywhere else. Whether that guidance is a cost or an asset
depends on your harness: it is written once by the framework, versioned with it,
and loaded selectively rather than all at once. But it is not free, it is far
larger than the code it describes, and a comparison that showed only the cheap
number would be picking the flattering one. Both rows are in the table.
We also measured what the same feature addition costs an AI coding agent (Claude Code / Sonnet 5, three scored trials per framework and arm, blind automated acceptance including a hidden HTTP smoke). On the July snapshot every framework shipped a working feature in every trial, with costs from $2.03 (hono) to $5.98 (adonisjs). The experiment then became a case study in documentation engineering for agents: when Guren v2.0.0's breaking majors were re-tested the day they were published — new APIs the model has no training data for — agents with the scaffold's shipped guidance went 3/3 at a 29% lower median cost, while a stripped-guidance baseline passed only 1/3. Full methodology, round-by-round history (including the round where an earlier harness iteration did not help), honest limitations, and reproduction scripts: agent-eval/.
The two Inertia-SSR implementations (guren/, adonisjs/) also make a clean
runtime benchmark: the same app on Bun vs Node. Interleaved oha rounds
against production builds with identically seeded databases put Guren at
2.3× the throughput on the SSR HTML path and 3.5× on the Inertia JSON
path, with a 1.8× faster cold start (GitHub Actions, medians of 5 rounds).
Methodology, caveats, and how to reproduce it yourself with one click:
BENCHMARK.md.
Each implementation is written idiomatically, the way its framework's own documentation recommends, and each has its own README documenting setup, tests, and any SPEC deviations. If you think an implementation misrepresents your framework of choice, please open a PR — improving the competitors' implementations makes this comparison more useful, not less.
Known asymmetries we chose to keep, because they are each framework's default: Guren and AdonisJS are server-rendered (Inertia SSR), Hono and NestJS are client-side SPAs over JSON APIs, Next.js uses Server Components + Server Actions, and TanStack Start uses SSR'd routes + server functions. AdonisJS uses its first-party Lucid ORM instead of Drizzle (per SPEC). AdonisJS 7 was current at build time and requires Node ≥ 24. Test counts differ (11 / 18 / 26 / 29 / 17 / 21) although all cover the same SPEC §6 scenarios.
MIT