Build modern web apps with Bun, Elysia, React, and Eden Treaty
|
|
|
|
# Create a new FluxStack app
bunx create-fluxstack my-awesome-app
cd my-awesome-app
bun run devThat's it! Your full-stack app is running:
| Service | URL |
|---|---|
| 🌐 Frontend | http://localhost:5173 |
| ⚙️ Backend API | http://localhost:3000 |
| 📚 Swagger Docs | http://localhost:3000/swagger |
| 🩺 Health Check | http://localhost:3000/api/health |
# Create in current directory
mkdir my-app && cd my-app
bunx create-fluxstack .
bun run dev|
Runtime Bun >= 1.2 |
Backend Elysia.js 1.4 |
Frontend React 19 |
Build Vite 7 |
Styling Tailwind CSS 4 |
Language TypeScript 5.8 |
API Client Eden Treaty 1.3 |
Testing Vitest 3 |
graph TB
subgraph "🎨 Frontend Layer"
React[React 19 + Vite]
LiveClient[Live Components Client]
Eden[Eden Treaty]
end
subgraph "🔌 Communication"
HTTP[HTTP / REST]
WS[WebSocket]
end
subgraph "⚙️ Backend Layer"
Elysia[Elysia.js]
Routes[API Routes]
LiveServer[Live Components Server]
Rooms[Room System]
end
React --> Eden
Eden --> HTTP
HTTP --> Elysia
Elysia --> Routes
LiveClient --> WS
WS --> LiveServer
LiveServer --> Rooms
Click to expand directory structure
FluxStack/
├── 🔒 core/ # Framework Core (Read-Only)
│ ├── framework/ # FluxStack orchestrator
│ ├── server/ # Elysia plugins, middleware, live engine
│ ├── client/ # Vite integration, Live hooks, providers
│ ├── cli/ # CLI commands & generators
│ ├── plugins/ # Built-in plugins (Swagger, Vite, etc.)
│ ├── types/ # Framework type definitions
│ └── utils/ # Logger, config schema, errors
│
├── 👨💻 app/ # Your Application Code
│ ├── server/ # Backend (Elysia + Bun)
│ │ ├── controllers/ # Business logic
│ │ ├── routes/ # API endpoints + schemas
│ │ ├── live/ # Live Components (server-side)
│ │ └── app.ts # Elysia app instance (Eden Treaty export)
│ │
│ ├── client/ # Frontend (React + Vite)
│ │ └── src/
│ │ ├── components/ # React components
│ │ ├── pages/ # Route pages
│ │ ├── live/ # Live Components (client-side)
│ │ └── lib/ # Eden Treaty client
│ │
│ └── shared/ # Shared type definitions
│
├── ⚙️ config/ # Declarative Configuration
│ ├── system/ # Config files (app, server, db, logger, etc.)
│ ├── fluxstack.config.ts # FluxStack config
│ └── index.ts # Centralized exports
│
├── 🔌 plugins/ # Project Plugins (auto-discovered)
├── 🧪 tests/ # Test suite (unit + integration)
├── 🤖 LLMD/ # LLM-Optimized Documentation
└── 🐳 Dockerfile # Multi-stage production buildReal-time WebSocket components with automatic state synchronization between server and client. Define state and logic on the server, interact with it from React — updates sync instantly via WebSocket.
// app/server/live/LiveCounter.ts
import { LiveComponent } from '@/core/server'
export class LiveCounter extends LiveComponent<{
count: number
}> {
static defaultState = { count: 0 }
async increment() {
this.state.count++ // auto-syncs via Proxy
return { success: true }
}
async decrement() {
this.state.count--
return { success: true }
}
async reset() {
this.state.count = 0
return { success: true }
}
} |
// app/client/src/live/CounterDemo.tsx
import { Live } from '@/core/client'
import { LiveCounter } from '@server/live/LiveCounter'
export function CounterDemo() {
const counter = Live.use(LiveCounter, {
room: 'global-counter',
initialState: LiveCounter.defaultState
})
return (
<div>
<span>{counter.$state.count}</span>
<button
onClick={() => counter.increment()}
disabled={counter.$loading}
>
+
</button>
<button onClick={() => counter.decrement()}>
-
</button>
<span>
{counter.$connected ? '🟢' : '🔴'}
</span>
</div>
)
} |
The Live.use() hook returns a Proxy object with full access to server state and actions:
const component = Live.use(MyComponent)
// State access
component.$state // Full state object
component.myProp // Direct property access via Proxy
component.$connected // Boolean - WebSocket connected?
component.$loading // Boolean - action in progress?
component.$error // Error message or null
// Actions
await component.myAction() // Call server method (type-safe)
component.$set('key', val) // Set a single property
// Form field binding
<input {...component.$field('email', { syncOn: 'change', debounce: 500 })} />
<input {...component.$field('name', { syncOn: 'blur' })} />
await component.$sync() // Manual sync for deferred fields
// Room events
component.$room.emit('event', data)
component.$room.on('message', handler)Multi-room real-time communication for Live Components — users in the same room share events.
|
Server: join rooms and emit events // app/server/live/ChatRoom.ts
export class ChatRoom extends LiveComponent<State> {
async joinRoom(payload: { roomId: string }) {
this.$room(payload.roomId).join()
this.$room(payload.roomId).on('message:new', (msg) => {
this.setState({
messages: [...this.state.messages, msg]
})
})
return { success: true }
}
async sendMessage(payload: { text: string }) {
const message = { id: Date.now(), text: payload.text }
this.setState({
messages: [...this.state.messages, message]
})
this.$room('chat').emit('message:new', message)
return { success: true }
}
} |
HTTP API for external integrations # Send a message to a room via API
curl -X POST \
http://localhost:3000/api/rooms/general/messages \
-H "Content-Type: application/json" \
-d '{"user": "Bot", "text": "Hello from API!"}'
# Emit a custom event to a room
curl -X POST \
http://localhost:3000/api/rooms/tech/emit \
-H "Content-Type: application/json" \
-d '{
"event": "notification",
"data": {"type": "alert", "msg": "Deploy done!"}
}'Rooms are accessible both from Live Components (WebSocket) and via REST API for webhooks, bots, and external services. |
Declarative auth for Live Components with role-based access control.
|
Server: protect components and actions // app/server/live/AdminPanel.ts
export class AdminPanel extends LiveComponent<State> {
static componentName = 'AdminPanel'
static defaultState = { users: [] }
// Component requires admin role
static auth = {
required: true,
roles: ['admin']
}
// Per-action permissions
static actionAuth = {
deleteUser: { permissions: ['users.delete'] }
}
async deleteUser(payload: { userId: string }) {
// Access user info via $auth
console.log(`${this.$auth.user?.id} deleting user`)
return { success: true }
}
} |
Client: authenticate dynamically import { useLiveComponents } from '@/core/client'
function LoginButton() {
const { authenticated, authenticate } = useLiveComponents()
const handleLogin = async () => {
await authenticate({ token: 'my-jwt-token' })
// Components auto re-mount with new auth!
}
return (
<button onClick={handleLogin}>
{authenticated ? 'Logged in' : 'Login'}
</button>
)
}Components that fail with |
Eden Treaty infers types from Elysia route definitions automatically. No manual DTOs.
// app/server/routes/users.routes.ts
import { Elysia, t } from 'elysia'
export const userRoutes = new Elysia({ prefix: '/users' })
.post('/', ({ body }) => createUser(body), {
body: t.Object({
name: t.String(),
email: t.String({ format: 'email' })
}),
response: t.Object({
success: t.Boolean(),
user: t.Optional(t.Object({
id: t.Number(),
name: t.String(),
email: t.String()
}))
})
}) |
// app/client/src/lib/eden-api.ts
import { api } from './eden-api'
// TypeScript knows all types automatically!
const { data, error } = await api.users.post({
name: 'Ada Lovelace', // ✅ string
email: 'ada@example.com' // ✅ string (email)
})
if (data?.user) {
console.log(data.user.name) // ✅ string
console.log(data.user.id) // ✅ number
} |
Benefits:
- ✅ Zero Manual Types — Types flow automatically from backend to frontend
- ✅ Full Autocomplete — IntelliSense in your IDE
- ✅ Refactor Friendly — Change backend schema, frontend updates automatically
Laravel-inspired config system with schema validation and full type inference.
// config/system/app.config.ts
import { defineConfig, config } from '@/core/utils/config-schema'
const appConfigSchema = {
name: config.string('APP_NAME', 'FluxStack', true),
port: config.number('PORT', 3000, true),
env: config.enum('NODE_ENV', ['development', 'production', 'test'] as const, 'development', true),
debug: config.boolean('DEBUG', false),
} as const
export const appConfig = defineConfig(appConfigSchema)
// appConfig.port → number, appConfig.env → "development" | "production" | "test"All environment variables are validated at boot time. See .env.example for available options.
Three-layer plugin architecture with security-first design:
| Layer | Location | Auto-discovered | Trusted |
|---|---|---|---|
| 🔒 Built-in | core/plugins/ |
No (manual .use()) |
✅ Yes |
| 📁 Project | plugins/ |
✅ Yes | ✅ Yes |
| 📦 NPM | node_modules/ |
❌ No (opt-in) | 🔒 Whitelist required |
NPM plugins are blocked by default. To add one safely:
bun run cli plugin:add fluxstack-plugin-auth
# Audits the package, installs it, and adds it to the whitelist
bun run dev # Full-stack with hot reload
bun run dev:frontend # Frontend only (port 5173)
bun run dev:backend # Backend only (port 3001) |
bun run build # Production build
bun run start # Start production server |
bun run test # Run tests (Vitest)
bun run test:ui # Vitest with browser UI
bun run test:coverage # Coverage report
bun run typecheck:api # Strict TypeScript check |
bun run cli # CLI interface
bun run make:component # Generate a Live Component
bun run sync-version # Sync version across files |
Default routes included in the demo app (React Router v7):
| Route | Page |
|---|---|
/ |
Home |
/counter |
Live Counter |
/form |
Live Form |
/upload |
Live Upload |
/auth |
Auth Demo |
/api-test |
Eden Treaty Demo |
Copy .env.example to .env and adjust as needed:
cp .env.example .envKey variables
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Backend server port |
HOST |
localhost |
Server host |
FRONTEND_PORT |
5173 |
Vite dev server port |
NODE_ENV |
development |
Environment |
LOG_LEVEL |
info |
Logging level |
CORS_ORIGINS |
localhost:3000,localhost:5173 |
Allowed CORS origins |
SWAGGER_ENABLED |
true |
Enable Swagger UI |
SWAGGER_PATH |
/swagger |
Swagger UI path |
See .env.example for the full list.
# Build
docker build -t fluxstack-app .
# Run
docker run -p 3000:3000 fluxstack-appThe Dockerfile uses a multi-stage build with oven/bun:1.2-alpine and runs as a non-root user.
| Feature | FluxStack | Next.js | T3 Stack |
|---|---|---|---|
| Runtime | ✅ Bun (3x faster) | ❌ Node.js | ❌ Node.js |
| Backend | ✅ Elysia (ultra-fast) | ✅ tRPC | |
| Type Safety | ✅ Eden Treaty (auto) | ✅ tRPC | |
| Real-Time | ✅ Live Components built-in | ||
| API Docs | ✅ Auto-generated Swagger | ❌ Manual | ❌ Manual |
| Config System | ✅ Declarative + validation | ||
| Docker | ✅ Multi-stage ready |
|
macOS / Linux: curl -fsSL https://bun.sh/install | bashWindows: powershell -c "irm bun.sh/install.ps1 | iex" |
⚠️ Important: FluxStack requires Bun. Node.js is not supported as a runtime.
bunx create-fluxstack@latest |
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Commit your changes (
git commit -m 'Add my feature') - Push to the branch (
git push origin feature/my-feature) - Open a Pull Request
Please open an issue first to discuss larger changes.
MIT - Marcos Brendon De Paula
Made with ❤️ by the FluxStack Team
Star ⭐ this repo if you find it helpful!