DEV Community

Cover image for Building Full-Stack Apps with Bun, React, and Hono: A Modern Approach
Sai Madhan
Sai Madhan

Posted on

Building Full-Stack Apps with Bun, React, and Hono: A Modern Approach

I've been exploring Bun lately and I'm impressed by its capabilities. When I decided to try building a full-stack application using Bun with React and Hono, I discovered a surprisingly elegant pattern that I wanted to share.

If you're looking for a modern, performant way to build full-stack applications, this combination might be exactly what you need.

Why This Stack?

  • Bun: Blazing fast JavaScript runtime with built-in bundling and excellent React support
  • React: The frontend framework we all know and love
  • Hono: Lightweight, fast web framework with excellent TypeScript support and middleware ecosystem

Setting Up Your Full-Stack App

Let's start by creating a new Bun application with React support:

bun init --react=shadcn
Enter fullscreen mode Exit fullscreen mode

This gives us a React application with shadcn/ui support out of the box. But here's where it gets interesting—Bun's routing system allows us to seamlessly integrate a Hono API server.

The Setup: Combining the Best of Both Worlds

Here's how we can set up our server to handle both API routes and React serving:

import { serve } from "bun";
import { Hono } from "hono";
import index from "./index.html";
import API from "./api";

const app = new Hono();
app.route("/api/", API);

const server = serve({
  routes: {
    "/api/*": app.fetch,  // API routes handled first
    "/*": index,          // React app serves everything else
  },
  development: process.env.NODE_ENV !== "production" && {
    hmr: true,
    console: true,
  },
});

console.log(`🚀 Server running at ${server.url}`);
Enter fullscreen mode Exit fullscreen mode

That's it! By mapping /api/* to app.fetch, we get a clean separation between our API and frontend, with both served from the same process.

Alternative Approaches

You could certainly build your API using Bun's built-in methods directly in the routes configuration:

const server = serve({
  routes: {
    "/api/users": {
      async GET(req) {
        return Response.json({ users: [...] });
      },
    },
    "/*": index,
  },
});
Enter fullscreen mode Exit fullscreen mode

Or use Hono's JSX capabilities to build React-style components:

import { Hono } from 'hono'
import { jsx } from 'hono/jsx'

const app = new Hono()
app.get('/', (c) => {
  return c.html(<h1>Hello Hono!</h1>)
})
Enter fullscreen mode Exit fullscreen mode

However, I prefer the approach outlined above because it gives us access to the full React ecosystem—Tailwind CSS, shadcn/ui, and countless React libraries—while still providing a powerful backend with Hono's extensive middleware collection.

Project Structure

Here's how I like to organize these projects:

├── index.ts          # Main server file
├── main.tsx          # React entry point
├── index.html        # HTML template
├── app/
│   └── App.tsx       # React components
├── api/
│   ├── index.ts      # API router
│   └── v1/
│       └── index.ts  # Versioned API routes
Enter fullscreen mode Exit fullscreen mode

API Setup with Hono

api/index.ts:

import { Hono } from "hono";
import V1Routes from "./v1";

const API = new Hono();
API.route("/v1/", V1Routes);

export default API;
Enter fullscreen mode Exit fullscreen mode

api/v1/index.ts:

import { Hono } from "hono";

const V1Routes = new Hono();

V1Routes.get("/", (c) => c.json({ message: "Welcome to V1" }));

V1Routes.get("/books", (c) => 
  c.json({ 
    books: [
      { id: 1, title: "The Great Gatsby", author: "F. Scott Fitzgerald" },
      { id: 2, title: "To Kill a Mockingbird", author: "Harper Lee" },
      { id: 3, title: "1984", author: "George Orwell" }
    ]
  })
);

V1Routes.get("/users", (c) => 
  c.json({ 
    users: [
      { id: 1, name: "John Doe", email: "john@example.com" },
      { id: 2, name: "Jane Smith", email: "jane@example.com" }
    ]
  })
);

export default V1Routes;
Enter fullscreen mode Exit fullscreen mode

Adding Client-Side Routing

For a proper SPA experience, you'll want to add React Router:

bun add react-router-dom
Enter fullscreen mode Exit fullscreen mode

App.tsx:

import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/books" element={<Books />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}
Enter fullscreen mode Exit fullscreen mode

The beauty of this setup is that Bun's "/*": index route serves your React app for all non-API requests, giving you client-side routing that works perfectly with React Router.

One Important Detail

When using app.route() with this setup, make sure to include the trailing slash:

app.route("/api/", API); // ✅ Works
// app.route("/api", API); // ❌ Won't work
Enter fullscreen mode Exit fullscreen mode

However, individual route handlers work as expected without any special configuration:

V1Routes.get("/books", handler); // Works at /api/v1/books
Enter fullscreen mode Exit fullscreen mode

What You Get

With this setup, you get:

  • API endpoints at /api/v1/books, /api/v1/users, etc.
  • React app served from / with full client-side routing
  • Hot module replacement for React components
  • Single process serving both frontend and backend
  • Type safety across your entire stack
  • Full React ecosystem: Tailwind CSS, shadcn/ui, and thousands of React libraries
  • Hono's middleware ecosystem for authentication, CORS, logging, validation, and more
  • Best of both worlds: Modern frontend tooling with a powerful, flexible backend

Performance Benefits

This approach gives you several performance advantages:

  • Bun's speed: Both your API and frontend benefit from Bun's performance
  • No proxy overhead: Everything runs in the same process
  • Efficient bundling: Bun handles all the heavy lifting for your React app
  • Fast startup: Your entire stack boots up quickly

Conclusion

Building full-stack applications with Bun, React, and Hono is remarkably straightforward once you understand how all three interact with each other. The combination gives you modern tooling, excellent performance, and a clean development experience.

Whether you're building a personal project or a production application, this stack provides a solid foundation with room to grow. The integration feels natural, and you get the best of all three technologies without any awkward compromises.

Give it a try—I think you'll be pleasantly surprised by how well these tools work together!

Top comments (2)

Collapse
 
falconz profile image
Sai Madhan

Thanks for raising that point, @rishabhrpg ! In practice, there isn’t much overhead from using both Bun’s built-in router and Hono’s router together. Bun’s router is mainly handling static file serving and the catch-all for React, while Hono takes care of the API routes. Since Bun delegates /api/* requests directly to Hono’s fetch handler, the routing decision is resolved very quickly.

The React side is handled client-side with React Router, so once the initial request is served, navigation doesn’t involve Bun’s router at all. That means you get clean separation without noticeable slowdown.

That said, if you prefer absolute minimal layers, you could rely entirely on Bun’s router for APIs—but you’d lose Hono’s middleware ecosystem and TypeScript ergonomics, which are the main reasons I like this setup.

Collapse
 
rishabhrpg profile image
Rishabh

We are using two routers here.
Will the built-in router and Hono router slow down the routing?