<?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: Daiki Urata</title>
    <description>The latest articles on DEV Community by Daiki Urata (@7nohe).</description>
    <link>https://dev.to/7nohe</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%2F4084940%2Fa2694bf0-3bd4-4180-8ec2-6f46307dd8ba.png</url>
      <title>DEV Community: Daiki Urata</title>
      <link>https://dev.to/7nohe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/7nohe"/>
    <language>en</language>
    <item>
      <title>Bun has Elysia and Hono for APIs. What about fullstack apps?</title>
      <dc:creator>Daiki Urata</dc:creator>
      <pubDate>Sun, 20 Sep 2026 13:27:57 +0000</pubDate>
      <link>https://dev.to/7nohe/bun-has-elysia-and-hono-for-apis-what-about-fullstack-apps-2l17</link>
      <guid>https://dev.to/7nohe/bun-has-elysia-and-hono-for-apis-what-about-fullstack-apps-2l17</guid>
      <description>&lt;h2&gt;
  
  
  The question
&lt;/h2&gt;

&lt;p&gt;I have been using Bun since it first started getting attention, but I had never used it as a production runtime. Mostly scripts, small tools, and things that run on my own machine.&lt;/p&gt;

&lt;p&gt;One reason it never went further is the question this post is about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bun is fast and the tooling is good. So what do I actually build a real app with?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For APIs, the answer has been fairly settled for a while.&lt;/p&gt;

&lt;p&gt;Use Hono if you want Web Standards and the option to run the same code on Node, Deno, or Workers. Use Elysia if you want Bun-tuned performance and Eden's typed client.&lt;/p&gt;

&lt;p&gt;Both are excellent backend frameworks.&lt;/p&gt;

&lt;p&gt;But once you are building a fullstack application with users, a database, forms, background jobs, and mail, you still have to assemble a lot of the application stack yourself.&lt;/p&gt;

&lt;p&gt;I want to spell out what that assembly involves, and then show what I built so I would not have to make those decisions from scratch every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Bun itself gives you
&lt;/h2&gt;

&lt;p&gt;Bun has shipped a fullstack development server since 1.2.3.&lt;/p&gt;

&lt;p&gt;You can import an HTML file as a route entrypoint, and Bun bundles the scripts and styles it references. &lt;code&gt;Bun.serve()&lt;/code&gt; accepts a &lt;code&gt;routes&lt;/code&gt; object for API endpoints, and you get HMR in development.&lt;/p&gt;

&lt;p&gt;The same documentation is also clear about what it does not provide.&lt;/p&gt;

&lt;p&gt;SSR is not built in, API routes are not auto-discovered, and the feature as a whole is still marked as a work in progress.&lt;/p&gt;

&lt;p&gt;Bun itself does provide useful primitives such as database access, password hashing, and cookies. What it does not decide is the application architecture around them: how the project is structured, how models and migrations work, how authentication flows are implemented, or how server data reaches your frontend.&lt;/p&gt;

&lt;p&gt;That is a reasonable boundary for a runtime to draw. The framework and application architecture decisions are still yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Elysia and Hono already give you, and what they leave open
&lt;/h2&gt;

&lt;p&gt;Hono and Elysia already cover more than routing.&lt;/p&gt;

&lt;p&gt;Both provide validation primitives and ways to keep server-to-client communication type-safe. Hono has RPC and &lt;code&gt;hc&lt;/code&gt;, JSX and SSR helpers, and authentication middleware. Elysia has schema validation, 422 responses, Eden for end-to-end typed clients, and official plugins for things such as JWT and cron.&lt;/p&gt;

&lt;p&gt;So the problem is not that these capabilities are missing.&lt;/p&gt;

&lt;p&gt;The problem is that &lt;strong&gt;they do not choose the application architecture around those capabilities for you&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a typical fullstack product, you still need to decide things like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Database models and migrations&lt;/li&gt;
&lt;li&gt;A complete user authentication flow: registration, persistent sessions, OAuth, password reset, and email verification&lt;/li&gt;
&lt;li&gt;Application-wide validation and error response conventions&lt;/li&gt;
&lt;li&gt;How React pages receive server data: through a typed API client, SSR, or something else&lt;/li&gt;
&lt;li&gt;Durable background jobs, mail, cache, and application events&lt;/li&gt;
&lt;li&gt;Application-level testing conventions and setup&lt;/li&gt;
&lt;li&gt;A production build and deployment strategy for your target platform&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hono and Elysia provide good primitives for several of these.&lt;/p&gt;

&lt;p&gt;You still have to choose the libraries, decide how they fit together, define the directory structure, and decide how much of that architecture should stay consistent across projects.&lt;/p&gt;

&lt;p&gt;I got stuck in the same place every time.&lt;/p&gt;

&lt;p&gt;Which database library? Which frontend integration? How should the directories be laid out?&lt;/p&gt;

&lt;p&gt;I re-decided those three things from scratch on each project. The next project usually ended up somewhere else, so very little of that thinking carried forward.&lt;/p&gt;

&lt;p&gt;A lot of time disappeared before any feature code was written.&lt;/p&gt;

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

&lt;p&gt;Guren is a Laravel-shaped framework built on top of Hono.&lt;/p&gt;

&lt;p&gt;Every request goes through Hono's router, so the routing layer uses the same foundation as Hono itself.&lt;/p&gt;

&lt;p&gt;On top of that, Guren provides defaults for the application-level decisions described above.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You need&lt;/th&gt;
&lt;th&gt;In a Guren app&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Drizzle ORM with a Model API: &lt;code&gt;Post.where('published', true).get()&lt;/code&gt;, &lt;code&gt;bun run db:migrate&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bunx guren add auth&lt;/code&gt; scaffolds registration, login, sessions, and password flows. &lt;code&gt;bunx guren add oauth&lt;/code&gt; adds OAuth providers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;this.validateBody(schema)&lt;/code&gt; with a Zod schema, using a consistent application-wide 422 error convention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Inertia.js pages in React, with controller-to-page props checked by codegen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jobs, mail, cache, events&lt;/td&gt;
&lt;td&gt;Built in and enabled through providers when needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TestApp&lt;/code&gt; from &lt;code&gt;@guren/testing&lt;/code&gt;, providing an application-aware test environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;td&gt;Plugins for AWS Lambda, Vercel, and Cloudflare Workers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is a route, a controller, and a typed page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/web.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@guren/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PostController&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/app/Http/Controllers/PostController&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;registerWebRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PostController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PostController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/Http/Controllers/PostController.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@guren/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/app/Models/Post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CreatePostSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/app/Http/Validators/PostValidator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/.guren/pages.gen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;published&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createdAt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;desc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inertia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validateBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CreatePostSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/posts/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The React page for &lt;code&gt;pages.posts.Index&lt;/code&gt; declares a &lt;code&gt;Props&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;Codegen reads it and checks the controller's &lt;code&gt;this.inertia()&lt;/code&gt; call against it. If a prop is renamed on one side and not the other, &lt;code&gt;tsc&lt;/code&gt; fails instead of the browser rendering &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is not a replacement for Hono RPC or Eden.&lt;/p&gt;

&lt;p&gt;Hono RPC and Eden make an API boundary type-safe.&lt;/p&gt;

&lt;p&gt;Guren makes the controller-to-Inertia-page boundary type-safe.&lt;/p&gt;

&lt;p&gt;That means page-level server-to-frontend data can stay type-safe without introducing an API client layer just to move data from a controller into a React page.&lt;/p&gt;

&lt;p&gt;If you have written Laravel, the project structure is intentionally familiar:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app/Http/Controllers&lt;/code&gt;, &lt;code&gt;app/Models&lt;/code&gt;, &lt;code&gt;routes/web.ts&lt;/code&gt;, &lt;code&gt;db/schema.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Even if you have not used Laravel, the structure is useful on its own.&lt;/p&gt;

&lt;p&gt;A coding agent can predict where things belong, and &lt;code&gt;guren check&lt;/code&gt; and &lt;code&gt;guren audit&lt;/code&gt; can verify what it wrote.&lt;/p&gt;

&lt;p&gt;For example, they can check route-to-controller-to-page consistency, or flag mutating routes that are missing validation or authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bun-first, not Bun-only
&lt;/h2&gt;

&lt;p&gt;"for Bun" is easy to misread, so here is the precise version.&lt;/p&gt;

&lt;p&gt;A scaffolded application runs on Bun.&lt;/p&gt;

&lt;p&gt;Development uses &lt;code&gt;bun run dev&lt;/code&gt; under &lt;code&gt;bun --hot&lt;/code&gt;, tests run with &lt;code&gt;bun test&lt;/code&gt;, and SQLite uses &lt;code&gt;bun:sqlite&lt;/code&gt; by default. PostgreSQL and MySQL are also supported.&lt;/p&gt;

&lt;p&gt;If you opt in, &lt;code&gt;Bun.password&lt;/code&gt; can be used for Argon2id hashing.&lt;/p&gt;

&lt;p&gt;The default password hasher, however, is scrypt through &lt;code&gt;node:crypto&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means the same password hashes can also be verified on Node.js.&lt;/p&gt;

&lt;p&gt;The deployment plugins target AWS Lambda on the Node.js runtime, Vercel on its Bun runtime, and Cloudflare Workers with D1.&lt;/p&gt;

&lt;p&gt;I run guren.dev itself on Workers.&lt;/p&gt;

&lt;p&gt;One surprise was Cloudflare Workers bundle size.&lt;/p&gt;

&lt;p&gt;I had been shipping the prerendered documentation inside the Worker, and it had grown to 28,622 KiB uncompressed.&lt;/p&gt;

&lt;p&gt;Nothing was watching that number, so I only noticed once the deployment reached 96.3% of the compressed size limit in force at the time.&lt;/p&gt;

&lt;p&gt;Moving the documentation to Workers Static Assets reduced the Worker itself to 4,908 KiB. The deployment now also fails if the bundle exceeds a defined size budget.&lt;/p&gt;

&lt;p&gt;On 2026-09-04, the Workers size limit changed to 64 MiB uncompressed, so the same Worker now sits at roughly 7.5% of that limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you should not use this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A small API with no database or users: plain Hono or Elysia. Use Elysia if you specifically want Eden's typed client.&lt;/li&gt;
&lt;li&gt;An application where a thin API plus Hono RPC or Eden already gives you everything you need: you probably do not need Guren.&lt;/li&gt;
&lt;li&gt;A content site or storefront where React rendering itself is the product: Next.js.&lt;/li&gt;
&lt;li&gt;A team already happy with Laravel or Rails: stay there.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx create-guren-app my-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
bun run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The longer version of this comparison is at &lt;a href="https://guren.dev/docs/guides/bun?ref=devto" rel="noopener noreferrer"&gt;Fullstack on Bun&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The code is at &lt;a href="https://github.com/gurenjs/guren" rel="noopener noreferrer"&gt;github.com/gurenjs/guren&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are running something fullstack on Bun today, I would like to know what your stack looks like and which parts you still ended up assembling yourself.&lt;/p&gt;

</description>
      <category>bunjs</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Why Guren Is Built on Hono, and What We Stack on Top of It</title>
      <dc:creator>Daiki Urata</dc:creator>
      <pubDate>Wed, 26 Aug 2026 13:42:53 +0000</pubDate>
      <link>https://dev.to/7nohe/why-guren-is-built-on-hono-and-what-we-stack-on-top-of-it-1i9j</link>
      <guid>https://dev.to/7nohe/why-guren-is-built-on-hono-and-what-we-stack-on-top-of-it-1i9j</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;This post explains why Guren, a Laravel-inspired fullstack TypeScript framework running on Bun, uses Hono as its HTTP layer, and what we build on top of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gurenjs/guren" rel="noopener noreferrer"&gt;https://github.com/gurenjs/guren&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Guren uses Hono for HTTP handling, Drizzle ORM for the database, and Inertia.js to connect the frontend. That said, if you're writing application code in Guren, you'll rarely touch Hono's API directly. Guren's &lt;code&gt;Controller&lt;/code&gt; and &lt;code&gt;Router&lt;/code&gt; sit on top of Hono and hide it from you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inertia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what a Guren controller looks like. There's a Hono &lt;code&gt;Context&lt;/code&gt; running underneath, but you don't need to think about it while writing this.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Why Hono
&lt;/h2&gt;

&lt;p&gt;Guren's design principle is "Bun-first, but deploy anywhere." The dev server and test runner use Bun's native APIs directly, so day-to-day development is optimized for Bun. At the same time, we care just as much about being able to deploy to AWS Lambda, Cloudflare Workers, Vercel, or wherever you need in production.&lt;/p&gt;

&lt;p&gt;Why Bun over Node.js, then? Simply because it's faster. In a benchmark comparing two identical Inertia SSR apps (&lt;a href="https://github.com/gurenjs/framework-comparison" rel="noopener noreferrer"&gt;framework-comparison&lt;/a&gt;), Guren gets 2.3x the throughput on SSR pages, 3.5x on JSON routes, and 1.8x faster cold starts. The app code is identical, so the difference comes down to the runtime.&lt;/p&gt;

&lt;p&gt;Hono was built from the start to run identically across runtimes, with Cloudflare Workers as its original target. It's tested against each runtime in CI, and that investment in portability shows. Since it's built on the Web Standard Request/Response, it runs on Cloudflare Workers, Deno, and Bun, as well as Node.js. It works fine on hosting platforms like Vercel too.&lt;/p&gt;

&lt;p&gt;Guren actually ships deployment support for AWS Lambda (&lt;code&gt;@guren/plugin-lambda&lt;/code&gt;), Cloudflare Workers (&lt;code&gt;@guren/plugin-cloudflare&lt;/code&gt;, with D1), and Vercel (&lt;code&gt;@guren/plugin-vercel&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lambda.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createLambdaHandler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@guren/core/lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLambdaHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploying to Lambda comes down to these few lines. Hono's thin abstraction is exactly what makes it easy for us to write these per-runtime adapters ourselves. It's a small thing, but it matters. If Guren's foundation were tied more tightly to Bun-specific behavior, this would have worked against that design principle.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Type safety that runs from the database to the frontend
&lt;/h2&gt;

&lt;p&gt;Hono has an RPC feature (HC) that infers types for your path params, query, request body, and response. That gives you a fully type-safe JSON API client. But Hono is a thin HTTP layer, and a database or ORM isn't part of its scope. RPC makes the shape of a JSON request/response type-safe. Where that shape actually comes from — a database schema, or a hand-written type — is outside what Hono is responsible for.&lt;/p&gt;

&lt;p&gt;Guren is a batteries-included framework with Drizzle ORM built in from the start, so the starting point for its types can sit further upstream, at the database schema itself. Let's walk through how a type actually flows from that schema all the way to the frontend props.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database schema
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// db/schema.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pgTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;serial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineModel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@guren/orm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/db/schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PostRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$inferSelect&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;defineModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you build a &lt;code&gt;Model&lt;/code&gt; with &lt;code&gt;defineModel(posts)&lt;/code&gt;, the return type of &lt;code&gt;Post.findOrFail(id)&lt;/code&gt; is inferred straight from the &lt;code&gt;posts&lt;/code&gt; table schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource
&lt;/h3&gt;

&lt;p&gt;Think of a Resource as the DTO (Data Transfer Object) pattern. It's a dedicated class that transforms a raw database value into the shape you actually want to send back, instead of exposing it as-is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/Http/Resources/PostResource.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@guren/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PostRecord&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/app/Models/Post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PostResourceData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostResource&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PostRecord&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;PostResourceData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Post&lt;/code&gt; model's type comes from the table definition in &lt;code&gt;db/schema.ts&lt;/code&gt;, as &lt;code&gt;typeof posts.$inferSelect&lt;/code&gt;. That becomes &lt;code&gt;PostRecord&lt;/code&gt;, the input type for &lt;code&gt;PostResource&lt;/code&gt;. On the output side, &lt;code&gt;PostResourceData&lt;/code&gt; is defined once inside &lt;code&gt;PostResource&lt;/code&gt; and then just imported wherever it's needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Routing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/web.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PostController&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/app/Http/Controllers/PostController&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PostController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;show&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts.show&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This wires the &lt;code&gt;/:id&lt;/code&gt; URL to &lt;code&gt;PostController.show&lt;/code&gt; and names the route &lt;code&gt;posts.show&lt;/code&gt;. The &lt;code&gt;pages.posts.Show&lt;/code&gt; that &lt;code&gt;PostController.show&lt;/code&gt; calls via &lt;code&gt;this.inertia(pages.posts.Show, ...)&lt;/code&gt; actually comes from the file path &lt;code&gt;resources/js/pages/posts/Show.tsx&lt;/code&gt;, not from this route name, but by convention the two are kept in sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/Http/Validators/PostValidator.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PostIdParamSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;positive&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Controller
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validateParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PostIdParamSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inertia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PostResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;id&lt;/code&gt; goes through &lt;code&gt;validateParams&lt;/code&gt; with a Zod schema (&lt;code&gt;PostIdParamSchema&lt;/code&gt;) first, so it's guaranteed to be a number before it ever reaches &lt;code&gt;Post.findOrFail&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  View (the page component)
&lt;/h3&gt;

&lt;p&gt;The only thing written by hand here is the &lt;code&gt;PostResourceData&lt;/code&gt; interface. The React page component just imports that same type straight from the resource file and uses it as its &lt;code&gt;Props&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// resources/js/pages/posts/Show.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PostResourceData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/app/Http/Resources/PostResource&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PostResourceData&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;bunx guren codegen&lt;/code&gt; parses the &lt;code&gt;Props&lt;/code&gt; interface declared in the page with a Babel AST and folds it into &lt;code&gt;.guren/pages.gen.ts&lt;/code&gt;. It then checks whatever you pass into &lt;code&gt;this.inertia(pages.posts.Show, { post: ... })&lt;/code&gt; against it at compile time. Passing a page something it doesn't expect fails at build time.&lt;/p&gt;

&lt;p&gt;We also generate a typed API client (&lt;code&gt;routes.gen.ts&lt;/code&gt; / &lt;code&gt;api-client.gen.ts&lt;/code&gt;) for when you're consuming Guren as a JSON API. But Guren's real strength isn't there. It's that the type flows from the database schema all the way to the frontend props, without having to rewrite it at every layer it crosses. That falls outside the scope of a standalone HTTP library like Hono or Elysia, since neither one dictates an ORM or a frontend integration. It's less that Guren is "wider than Hono's RPC" and more that the two cover different ground entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. AI agent tooling stacks on the same spot
&lt;/h2&gt;

&lt;p&gt;Hono actually has its own AI-agent-oriented CLI, &lt;a href="https://github.com/honojs/cli" rel="noopener noreferrer"&gt;honojs/cli&lt;/a&gt;, with commands like &lt;code&gt;docs&lt;/code&gt; (browse documentation), &lt;code&gt;search&lt;/code&gt; (search documentation), &lt;code&gt;request&lt;/code&gt; (test requests), and &lt;code&gt;optimize&lt;/code&gt; (bundle optimization). Since Hono itself doesn't dictate an application structure, those commands stay centered on documentation and testing. Guren dictates a structure — the MVC pattern — so it can stack CLI commands that verify and summarize that structure on top.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx guren context User   &lt;span class="c"&gt;# bundles the User model, its routes, pages, resource, policy, and related docs into one&lt;/span&gt;
bunx guren check           &lt;span class="c"&gt;# checks route/controller/page consistency and architecture boundaries&lt;/span&gt;
bunx guren audit           &lt;span class="c"&gt;# security audit: missing validation, missing authorization, raw SQL, leaked secrets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;guren context User&lt;/code&gt; bundles the &lt;code&gt;User&lt;/code&gt; model's definition, its related routes, pages, resource, policy, and related docs into a single Markdown document. When you ask an AI agent to work on anything User-related, instead of having it search the whole codebase every time, you hand it the context it needs with this one command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;guren check&lt;/code&gt; mechanically checks whether route definitions, controllers, and Inertia pages line up, and whether a registered route is actually reachable from the registrar — consistency checks that assume the whole framework's structure. &lt;code&gt;guren audit&lt;/code&gt; checks whether data-changing routes — &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, and the like — have validation and authorization attached, and scans for raw SQL or leaked secrets.&lt;/p&gt;

&lt;p&gt;Neither of these is something Hono on its own is meant to cover. Hono just handles HTTP, so a check like "does this route have authorization attached" or "what files relate to this model" — one that assumes a whole-framework structure — is outside what it's responsible for. It's precisely because Guren dictates the shape of an application that it can stack tools on top that reason about that shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The hypothesis that conventions help
&lt;/h2&gt;

&lt;p&gt;There's a hypothesis behind this: the more a framework enforces conventions about where things live — the way Rails or Laravel does — the more effective agent guidance like rules and skills becomes. This isn't just my own hunch — both the Rails and Laravel camps say the same thing.&lt;/p&gt;

&lt;p&gt;Rails' own &lt;a href="https://rubyonrails.org/ai" rel="noopener noreferrer"&gt;Rails and AI&lt;/a&gt; page argues that Convention over Configuration is exactly what lets an agent produce idiomatic code from a short prompt. DHH &lt;a href="https://x.com/dhh/status/2018574874675929544" rel="noopener noreferrer"&gt;put it this way on X&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Convention over configuration set the path for 20+ years of great training data for AI to use today. Not only does this mean agents do great with Rails, but also that squishy humans can quickly and confidently review the output without a jungle of distracting boilerplate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Laravel's Taylor Otwell made essentially the same point in an interview on the Laravel blog:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I think it matters in the sense that LLMs still do well with things that are easy to parse and understand. Frameworks that lean into conventions and structure do well with LLMs. There's this very conventional structure to the projects where it's like, there's a models' directory, there's a controllers' directory. It's very discoverable.&lt;br&gt;
— &lt;a href="https://laravel.com/blog/laravel-ai-sdk-boost-or-mcp-which-tool-do-you-need" rel="noopener noreferrer"&gt;Laravel AI SDK, Boost, or MCP: Which Tool Do You Need?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We measured something adjacent for Guren. What we tested wasn't "with conventions vs. without" — it was "with agent guidance (rules, skills, CLI tools) vs. without," on top of the same Guren conventions in both conditions. Across 360 cells (3 models × 20 tasks × with/without the harness × 3 runs), Sonnet 5 shipped with the harness at −28% turns and −25% cost, and &lt;code&gt;guren check&lt;/code&gt; got invoked 119 out of 180 runs with the harness versus 15 out of 180 without it. What that shows is that guidance pays off on top of a conventional framework — it doesn't isolate the effect of the conventions themselves. Details are in &lt;a href="https://guren.dev/blog/agents-on-guren-the-first-benchmark-report" rel="noopener noreferrer"&gt;the full report&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There's research pointing the other way, too. &lt;a href="https://arxiv.org/html/2605.06445" rel="noopener noreferrer"&gt;Constraint decay: The Fragility of LLM Agents in Backend Code Generation&lt;/a&gt; found that minimal frameworks like Express, Koa, and Flask scored around 50% Assert%, while convention-driven ones like Django and FastAPI dropped to around 25%. The cause: agents can't reliably infer a framework's implicit behavior — automatic validation from type hints, convention-based auto-discovery — from pretraining data alone.&lt;/p&gt;

&lt;p&gt;The framework that came in last in that benchmark was, of all things, Hono — supposedly one of the least convention-heavy frameworks in the set — at 18.5%. But by the paper's own account, that's not down to convention overload — it's that setting up the edge-runtime compatibility adapter (&lt;code&gt;@hono/node-server&lt;/code&gt;) is thinly represented in training data, a different failure mode entirely. Citing Hono's ranking as evidence that "more convention means worse performance" mixes up two different causes.&lt;/p&gt;

&lt;p&gt;The agents in that benchmark were given no framework-specific guidance at all — the result reflects what happens when following convention is left entirely to an LLM's pretraining. Rails and Laravel can lean on "20+ years of public code baked into the training data," but Guren is too young a framework to have that foundation. What it has instead is &lt;code&gt;guren context&lt;/code&gt;, &lt;code&gt;guren check&lt;/code&gt;, and &lt;code&gt;guren audit&lt;/code&gt;, which externalize and verify convention-following mechanically rather than leaving it to the model's implicit knowledge. Guren's answer isn't to thin out its conventions, and it isn't to wait for those conventions to get baked into training data — it's to make the conventions checkable by tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;To sum up, in my own opinion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Guren picked Hono as its foundation because it fit our "not locked into Bun, deployable to Lambda and Cloudflare Workers" design goal&lt;/li&gt;
&lt;li&gt;Guren adds end-to-end type safety that runs from the database schema all the way to Inertia props, at the framework layer&lt;/li&gt;
&lt;li&gt;Because Guren dictates a structure — the MVC pattern — it can also stack AI agent tooling like &lt;code&gt;guren context&lt;/code&gt;, &lt;code&gt;guren check&lt;/code&gt;, and &lt;code&gt;guren audit&lt;/code&gt; on top&lt;/li&gt;
&lt;li&gt;Hono's thin abstraction is what keeps Guren's own runtime adapters simple to write&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building on a minimal library like Hono puts the real design decisions on what you stack on top of it. With Guren, the goal is to absorb those foundational and runtime-specific differences at the framework layer, so the person writing the app can just focus on business logic.&lt;/p&gt;

&lt;p&gt;If you're curious, take a look at the Guren repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gurenjs/guren" rel="noopener noreferrer"&gt;https://github.com/gurenjs/guren&lt;/a&gt;&lt;/p&gt;

</description>
      <category>hono</category>
      <category>typescript</category>
      <category>bunjs</category>
      <category>ai</category>
    </item>
    <item>
      <title>I'm building Guren, a fullstack TypeScript framework for the AI-agent era</title>
      <dc:creator>Daiki Urata</dc:creator>
      <pubDate>Wed, 19 Aug 2026 12:22:30 +0000</pubDate>
      <link>https://dev.to/7nohe/im-building-guren-a-fullstack-typescript-framework-for-the-ai-agent-era-47k6</link>
      <guid>https://dev.to/7nohe/im-building-guren-a-fullstack-typescript-framework-for-the-ai-agent-era-47k6</guid>
      <description>&lt;p&gt;&lt;a href="https://guren.dev" rel="noopener noreferrer"&gt;Guren&lt;/a&gt; is a fullstack TypeScript framework for Bun. I started it because I wanted Laravel's shape in TypeScript, and I kept going for a different reason: once I was handing most of the code to agents, what I wanted from a framework was a way to check what came back.&lt;/p&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/gurenjs" rel="noopener noreferrer"&gt;
        gurenjs
      &lt;/a&gt; / &lt;a href="https://github.com/gurenjs/guren" rel="noopener noreferrer"&gt;
        guren
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Guren is a Bun-native TypeScript MVC framework that unites Laravel-like ergonomics with Hono, Inertia.js, React, and Drizzle ORM, aiming to deliver a fast, elegant full-stack workflow that keeps frontend and backend work in sync.
    &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;Guren&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://x.com/gurenjs" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7c9befc51eb8e2d5190b6036697fdcfb334005de6837e5affd20e38dd34a8feb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f666f6c6c6f772d253430677572656e6a732d626c61636b3f6c6f676f3d78" alt="X (Twitter)"&gt;&lt;/a&gt;
&lt;a href="https://github.com/gurenjs/guren/discussions" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/62a4474e6a73baecea7791d6143a58f79f6760106e864f144e9e5b89859531bd/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f64697363757373696f6e732f677572656e6a732f677572656e" alt="GitHub Discussions"&gt;&lt;/a&gt;
&lt;a href="https://github.com/gurenjs/guren/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a href="https://github.com/sponsors/7nohe" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2b4a42a2163b63add83eebd701834de0aadf204647ef113156726774cc0a2a0a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73706f6e736f72732f376e6f68653f6c6f676f3d67697468756273706f6e736f7273" alt="GitHub Sponsors"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fullstack TypeScript framework for the AI-agent era.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Laravel-style conventions, end-to-end type safety, and built-in agent introspection and verification — routing, controllers, ORM, authentication, and Inertia.js + React in one cohesive experience that humans and AI coding agents navigate from the same map.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;v2&lt;/strong&gt; — Stable. Breaking changes only in major releases, per the &lt;a href="https://github.com/gurenjs/guren/docs/en/guides/release-policy.md" rel="noopener noreferrer"&gt;release policy&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick Start&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 1. Scaffold a new app with authentication (dependencies install automatically)&lt;/span&gt;
bunx create-guren-app my-app --auth
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; my-app

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 2. Run migrations and seed the demo user (SQLite by default — no server needed)&lt;/span&gt;
bun run db:migrate
bun run db:seed

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 3. Start the dev server&lt;/span&gt;
bun run dev&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Open &lt;code&gt;http://localhost:3333&lt;/code&gt; and sign in at &lt;code&gt;/login&lt;/code&gt; with &lt;code&gt;demo@example.com&lt;/code&gt; / &lt;code&gt;secret&lt;/code&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Add features as you go&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;bunx guren add auth            &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Authentication&lt;/span&gt;
bunx guren add resource posts --fields &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;title:string,body:text&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;  &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; CRUD resource&lt;/span&gt;
bunx guren add queue           &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Background jobs&lt;/span&gt;&lt;/pre&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/gurenjs/guren" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I like the way Laravel and Rails let you build. A feature is a route, a controller, a model and a view, and authentication, queues, mail and validation are already wired together before you start.&lt;/p&gt;

&lt;p&gt;TypeScript has the parts. Hono for HTTP, Drizzle for the ORM, Zod for validation, Inertia and React for rendering, all of them good. What's missing is an agreed way to connect them, so every project ends up wiring it slightly differently, and I've written that wiring more times than I want to count.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistakes moved
&lt;/h2&gt;

&lt;p&gt;I hand most of that to coding agents now. Models got good enough that obviously broken output mostly stopped showing up, and what replaced it is harder to deal with: code that reads fine and is subtly wrong, arriving faster than I read carefully.&lt;/p&gt;

&lt;p&gt;It gets worse outside the training data. When I published v2.0.0, the new API shapes were younger than any model's training set, and the agents didn't slow down for that at all. They filled the gaps by guessing, and the guesses looked like working code.&lt;/p&gt;

&lt;p&gt;Fullstack TypeScript frameworks are not a new idea and several of the existing&lt;br&gt;
ones are good. What I couldn't find was one that treated agent generation and&lt;br&gt;
mechanical verification as a design input instead of something added on later, so&lt;br&gt;
I started building one.&lt;/p&gt;
&lt;h2&gt;
  
  
  What's in it
&lt;/h2&gt;

&lt;p&gt;Routing, controllers, ORM, authentication, and an Inertia.js + React frontend, as one experience. Nothing underneath is new: HTTP is Hono, the ORM is Drizzle, validation is Zod, rendering is Inertia + React + Vite. Guren doesn't ship its own ORM or its own validator, it gives those defaults a set of conventions.&lt;/p&gt;

&lt;p&gt;A controller looks about how you'd expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@guren/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/.guren/pages.gen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validateParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PostIdParamSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// throws a 404 on its own&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inertia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validateBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CreatePostSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// 422 on failure&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;userOrFail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                &lt;span class="c1"&gt;// 401 if anonymous&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;authorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Batteries are included in the Laravel sense: authentication with OAuth providers, queues and jobs, mail, events and listeners, cache, notifications, broadcasting, scheduling, storage, i18n, policies, console commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types that cross the boundary
&lt;/h2&gt;

&lt;p&gt;You bind a Zod schema to a route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts.store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PostPayloadSchema&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PostController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the form on the other side derives its type from that schema instead of restating it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PostFormData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;RouteBody&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ApiRoutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts.store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useForm&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PostFormData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a field to the schema on the server and the form's type follows. Rename the route and &lt;code&gt;route('posts.store')&lt;/code&gt; stops compiling. Page props work the same way, checked against the page component's own &lt;code&gt;Props&lt;/code&gt; interface, so a misspelled prop is a typecheck failure rather than a blank space on the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Letting the project explain itself
&lt;/h2&gt;

&lt;p&gt;Early on I ran an evaluation to see what a feature actually costs an agent on each framework. I expected Guren to come out cheap, because a framework that has already decided things for you should mean less code for the agent to write. The first bare measurement put it at a $5.54 median, 2.7 times Hono's.&lt;/p&gt;

&lt;p&gt;The stream analysis explained it. Agents were spending 17 to 46 percent of all their tool actions reverse engineering &lt;code&gt;@guren/*&lt;/code&gt; APIs out of &lt;code&gt;node_modules&lt;/code&gt; dist bundles, reading minified output to find out what a method is called. The&lt;br&gt;
implementation work itself was fine. What cost money was discovery, which makes sense in retrospect, since the model knows Hono and Next.js from training data and has never seen Guren. So I fixed the documentation and measured again, which brought it to $3.35.&lt;/p&gt;

&lt;p&gt;That's the problem &lt;code&gt;guren context&lt;/code&gt; exists to solve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Post&lt;/span&gt;

&lt;span class="gu"&gt;## Model — app/Models/Post.ts (table: `posts`)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Columns: id, title, excerpt, body, authorId
&lt;span class="p"&gt;-&lt;/span&gt; belongsTo: &lt;span class="sb"&gt;`author`&lt;/span&gt; → PostAuthorSummary

&lt;span class="gu"&gt;## Referenced by&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; User — hasMany &lt;span class="sb"&gt;`posts`&lt;/span&gt;

&lt;span class="gu"&gt;## Routes (8)&lt;/span&gt;
| Method | Path | Name | Action | Body |
|--------|------|------|--------|------|
| GET | /posts | posts.index | PostController.index | |
| GET | /posts/:id | posts.show | PostController.show | |
| POST | /posts | posts.store | PostController.store | { title: string; excerpt: string; body: string } |

&lt;span class="gu"&gt;## Pages (4)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; posts/Show — Props: &lt;span class="sb"&gt;`{ post: PostResourceData }`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; posts/Edit — Props: &lt;span class="sb"&gt;`{ post: PostFormValues | null postId: number ... }`&lt;/span&gt;

&lt;span class="gu"&gt;## Resource — app/Http/Resources/PostResource.ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One command, and &lt;code&gt;--json&lt;/code&gt; gives the machine-readable version. After the agent writes something, there are commands that check it: &lt;code&gt;guren check&lt;/code&gt; verifies that routes, controllers and pages still agree and that route files are actually wired into a registrar, &lt;code&gt;guren audit&lt;/code&gt; flags mutating routes with no validation or auth along with raw SQL and secrets, and &lt;code&gt;guren spec:generate&lt;/code&gt; regenerates the ER diagram, domain model and screen inventory from code so CI fails when they drift.&lt;br&gt;
None of it depends on trusting the agent, since the checks run against the repository either way.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the numbers say
&lt;/h2&gt;

&lt;p&gt;I publish the evaluation rather than describing it: the same feature task, on several frameworks, scored automatically by typecheck, the full test suite, and a hidden HTTP smoke the agent never sees.&lt;/p&gt;

&lt;p&gt;On the frameworks the model already knows well, every trial shipped, and Guren still sits at a $3.35 median against Hono's $2.03.&lt;/p&gt;

&lt;p&gt;The result I care about is from the day I published v2.0.0, when the breaking API changes were younger than any model's training data. With the guidance every new project scaffolds, agents passed 3 of 3 at a $4.90 median. With that guidance stripped out, 1 of 3 at $6.94. The failures guessed at API shapes v2 had changed, the paginated response structure and the testing assertions, and then wrote tests around the wrong guesses.&lt;/p&gt;

&lt;p&gt;Three trials per arm isn't a statistical claim.&lt;/p&gt;

&lt;p&gt;Every round is published, including one where a harness change I was confident about turned out to have quietly lost an earlier win. There's also a correction I had to make this month: an outside review found that the guidance an agent reads wasn't being counted in any of the metrics, sitting right under the section arguing Guren is cheap to read. It's 1,784 lines and 22,582 tokens, and it has its own row now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://guren.dev/blog/agents-on-guren-the-first-benchmark-report" rel="noopener noreferrer"&gt;The benchmark report&lt;/a&gt; has the detail, and &lt;a href="https://github.com/gurenjs/framework-comparison/tree/main/agent-eval" rel="noopener noreferrer"&gt;the raw rounds are here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;Development assumes Bun's toolchain, though deployment doesn't: your own Bun server, or the first-party plugins for AWS Lambda, Vercel and Cloudflare Workers.&lt;br&gt;
guren.dev is a Guren app, blog and admin screen included, running on Workers and D1. Building it turned up a handful of framework bugs, which is roughly why I built it there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx create-guren-app my-app &lt;span class="nt"&gt;--auth&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
bun run db:migrate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; bun run db:seed
bun run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLite is the default, so there's no database server to set up. Open &lt;code&gt;http://localhost:3333/login&lt;/code&gt;, sign in with &lt;code&gt;demo@example.com&lt;/code&gt; / &lt;code&gt;secret&lt;/code&gt;, and you have an authenticated app running.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to use it
&lt;/h2&gt;

&lt;p&gt;If your product is rendering-centric RSC-style React, Next.js is a better fit and I'd reach for it too. Plain Hono is lighter for anything that isn't really an MVC app, a webhook receiver being the obvious case, and if Bun is off the table on your developers' machines then none of this helps you. The docs have &lt;a href="https://guren.dev/docs/guides/why-guren" rel="noopener noreferrer"&gt;a longer version of this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The community is small. Mostly me, so far.&lt;/p&gt;

&lt;p&gt;If you like how Laravel or Rails feel and you're tired of rebuilding the same wiring in TypeScript, it shouldn't cost much to try.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/gurenjs/guren" rel="noopener noreferrer"&gt;https://github.com/gurenjs/guren&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://guren.dev/docs" rel="noopener noreferrer"&gt;https://guren.dev/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Feedback: GitHub Discussions&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>ai</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
