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 (0)