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)