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
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}`);
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,
},
});
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>)
})
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
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;
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;
Adding Client-Side Routing
For a proper SPA experience, you'll want to add React Router:
bun add react-router-dom
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>
);
}
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
However, individual route handlers work as expected without any special configuration:
V1Routes.get("/books", handler); // Works at /api/v1/books
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)
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.
We are using two routers here.
Will the built-in router and Hono router slow down the routing?