DEV Community

Cover image for Bun has Elysia and Hono for APIs. What about fullstack apps?
Daiki Urata
Daiki Urata

Posted on Originally published at 7nohe.dev AI-assisted

Bun has Elysia and Hono for APIs. What about fullstack apps?

The question

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.

One reason it never went further is the question this post is about:

Bun is fast and the tooling is good. So what do I actually build a real app with?

For APIs, the answer has been fairly settled for a while.

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.

Both are excellent backend frameworks.

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.

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.

What Bun itself gives you

Bun has shipped a fullstack development server since 1.2.3.

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

The same documentation is also clear about what it does not provide.

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.

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.

That is a reasonable boundary for a runtime to draw. The framework and application architecture decisions are still yours.

What Elysia and Hono already give you, and what they leave open

Hono and Elysia already cover more than routing.

Both provide validation primitives and ways to keep server-to-client communication type-safe. Hono has RPC and hc, 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.

So the problem is not that these capabilities are missing.

The problem is that they do not choose the application architecture around those capabilities for you.

For a typical fullstack product, you still need to decide things like:

  1. Database models and migrations
  2. A complete user authentication flow: registration, persistent sessions, OAuth, password reset, and email verification
  3. Application-wide validation and error response conventions
  4. How React pages receive server data: through a typed API client, SSR, or something else
  5. Durable background jobs, mail, cache, and application events
  6. Application-level testing conventions and setup
  7. A production build and deployment strategy for your target platform

Hono and Elysia provide good primitives for several of these.

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.

I got stuck in the same place every time.

Which database library? Which frontend integration? How should the directories be laid out?

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.

A lot of time disappeared before any feature code was written.

What I built

Guren is a Laravel-shaped framework built on top of Hono.

Every request goes through Hono's router, so the routing layer uses the same foundation as Hono itself.

On top of that, Guren provides defaults for the application-level decisions described above.

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

Here is a route, a controller, and a typed page:

// routes/web.ts
import { Router } from '@guren/core'
import PostController from '@/app/Http/Controllers/PostController'

export function registerWebRoutes(router: Router): void {
  router.get('/posts', [PostController, 'index'])
  router.post('/posts', [PostController, 'store'])
}
Enter fullscreen mode Exit fullscreen mode
// app/Http/Controllers/PostController.ts
import { Controller } from '@guren/core'
import { Post } from '@/app/Models/Post'
import { CreatePostSchema } from '@/app/Http/Validators/PostValidator'
import { pages } from '@/.guren/pages.gen'

export default class PostController extends Controller {
  async index() {
    const posts = await Post.where('published', true).orderBy('createdAt', 'desc').get()
    return this.inertia(pages.posts.Index, { posts })
  }

  async store() {
    const data = await this.validateBody(CreatePostSchema)
    const post = await Post.create(data)
    return this.redirect(`/posts/${post?.id ?? ''}`)
  }
}
Enter fullscreen mode Exit fullscreen mode

The React page for pages.posts.Index declares a Props interface.

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

This is not a replacement for Hono RPC or Eden.

Hono RPC and Eden make an API boundary type-safe.

Guren makes the controller-to-Inertia-page boundary type-safe.

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.

If you have written Laravel, the project structure is intentionally familiar:

app/Http/Controllers, app/Models, routes/web.ts, db/schema.ts.

Even if you have not used Laravel, the structure is useful on its own.

A coding agent can predict where things belong, and guren check and guren audit can verify what it wrote.

For example, they can check route-to-controller-to-page consistency, or flag mutating routes that are missing validation or authentication.

Bun-first, not Bun-only

"for Bun" is easy to misread, so here is the precise version.

A scaffolded application runs on Bun.

Development uses bun run dev under bun --hot, tests run with bun test, and SQLite uses bun:sqlite by default. PostgreSQL and MySQL are also supported.

If you opt in, Bun.password can be used for Argon2id hashing.

The default password hasher, however, is scrypt through node:crypto.

That means the same password hashes can also be verified on Node.js.

The deployment plugins target AWS Lambda on the Node.js runtime, Vercel on its Bun runtime, and Cloudflare Workers with D1.

I run guren.dev itself on Workers.

One surprise was Cloudflare Workers bundle size.

I had been shipping the prerendered documentation inside the Worker, and it had grown to 28,622 KiB uncompressed.

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.

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.

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.

When you should not use this

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

Try it

bunx create-guren-app my-app
cd my-app
bun run dev
Enter fullscreen mode Exit fullscreen mode

The longer version of this comparison is at Fullstack on Bun.

The code is at github.com/gurenjs/guren.

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.

Top comments (0)