Bamboo is a modern web framework for the Bun.sh JavaScript Runtime, designed for rapid development and a future where both humans and AI can build together. 🚀
⚠️ Note: Bamboo is under active development and its API is not yet stable. Use in production at your own risk!
Bamboo aims to be a "feature-rich" environment for both humans and artificial intelligence. The vision: make it easy for developers—and even LLMs—to generate new features quickly, using convention over configuration. This means less boilerplate, more productivity, and a framework that adapts to the future of software development.
- HTTP & WebSocket Routing
- Built-in Static Asset Handling (with directory remapping)
- WebSockets with Per-Connection State
- DevServer - Multi-process development dashboard with real-time monitoring
- Background Services (WIP)
- Distributed Messaging (WIP)
- Extensions System (WIP)
- Devhub: Built-in dashboard for multi-process dev, debugging, and more
- Storage System (WIP/Planned)
- External providers, web interface, and object authorization
- Rooms (Channels) API (WIP)
- Pub/Sub support
Bamboo is designed so that even an LLM can generate new features with minimal context. The goal is to make feature creation as simple and automated as possible, for both humans and AI.
Bamboo's DevServer provides a beautiful web dashboard for running and monitoring multiple development processes simultaneously. Perfect for full-stack development where you need to run API servers, frontend dev servers, database tools, and more.
import { devserver } from 'bamboo'
// Define your development processes
const processes: [name: string, script: string][] = [
['api', 'src/api.ts'],
['frontend', 'bun run --hot src/frontend.ts'],
['database', 'bunx prisma studio'],
['watcher', 'bun run --watch src/watcher.ts'],
]
// Start the devserver
devserver(processes)
// Import and run (this starts the dashboard)
import 'bamboo/src/devserver'
- Real-time Output: See live output from all processes in separate panels
- Process Control: Start, stop, and restart individual processes
- Beautiful UI: Modern, responsive dashboard with dark theme
- WebSocket Communication: Real-time updates without page refresh
- Graceful Shutdown: Properly terminates all processes on exit
Visit http://localhost:1337
to access the dashboard. Each process gets its own card with:
- Process name and status indicator
- Real-time output stream
- Control buttons (Restart, Stop, Clear)
- Connection status indicator
// devserver-example.ts
#!/usr/bin/env bun
import { devserver } from 'bamboo'
const processes: [name: string, script: string][] = [
['api', 'src/api.ts'],
['frontend', 'bun run --hot src/frontend.ts'],
['database', 'bunx prisma studio'],
['watcher', 'bun run --watch src/watcher.ts'],
]
devserver(processes)
import 'bamboo/src/devserver'
Run with: bun run devserver-example.ts
With TelegramClient
and TelegramServer
, you can broadcast messages across systems—a lightweight alternative to SQS, perfect for container-based deployments (like Fly.io).
Extensions let you customize the Bamboo engine. The devhub
extension (WIP) provides a dashboard for running multiple processes, debugging, and viewing request lifecycles.
devhub(engine, {
prismastudio: ['bunx', 'prisma', 'studio'],
frontend: {
cmd: ['bunx', '--bun', 'vite'],
cwd: join(cwd(), 'frontend'),
},
telegram: ['bunx', 'bamboo', 'telegram', '--serve'],
})
Bamboo makes it easy to manage both persistent (connection) and ephemeral (action) state for each WebSocket client.
💡 Tip: Use connection state for info that lasts (like user ID), and action state for info that's just for one request (like the room being joined).
1. On user join:
// When the user connects and logs in
endpoint.push('user.id', user.id); // Stays for the session
2. On join room action:
function joinRoomAction(endpoint, data) {
endpoint.stash('room.id', data.roomId); // Only for this action
const userId = endpoint.get('user.id');
const roomId = endpoint.fromStash('room.id');
addUserToRoom(userId, roomId);
return endpoint.json({ success: true, roomId });
}
3. On send message action:
function sendMessageAction(endpoint, data) {
const userId = endpoint.get('user.id'); // Persistent
const roomId = data.roomId; // Sent with each message
sendMessageToRoom(roomId, { userId, text: data.text });
return endpoint.json({ success: true });
}
🚀 Why? The user ID is needed for every action (so it's stored for the session), but the room ID is only needed for specific actions (like joining a room or sending a message), so you can pass it as needed or stash it for a single action.
Check out the /examples
and /test
folders for more sample code and usage patterns.
Contributions, ideas, and feedback are welcome! Please open an issue or pull request on GitHub.
Currently, there is a single instance of Engine
that is created when you call import {engine} from 'bamboo'
and this serves as the entire application's engine to work with. In the future, this needs to be changed to createEngine()
so that additional instances can be created if needed.
Unsure if this is possible, but with responses it's kind of messy looking when I have to call endpoint.json()
or anything else where I need to use an endpoint. I've been thinking about scopes and creating helper functions where the value of endpoint
is known to exist, so it just uses it without having to pass it in. This would make for simpler and cleaner code, I think.
Currently my investigation reveals that this is possible, but with TypeScript it may be tricky to implement without requiring // @ts-ignore
everywhere.
This is the concept:
class Engine {
val: string = '1'
constructor(handler: (val: string) => string) {
console.log('value:', this.val)
this.val = handler(this.val)
console.log('value:', this.val)
}
}
function json(data: any): string {
// We can use 'val' here from the parent scope.
return JSON.stringify(data)
}
new Engine((val) => {
return json({
val: val + '-yes-hello',
})
})
So that in the future our helper functions would look like below. I'll use the websocket stash as an example:
stash('profile.id', 1)
// Under the hood I imagine this looking something like:
function stash<T>(name: string, value?: T): T | null {
if (endpoint && endpoint instanceof Endpoint) {
if (value) {
endpoint.stash(name, value)
} else {
return endpoint.fromStash(name)
}
}
return null
}
// Other examples:
json({})
text('Hi')
status(404, 'Not Found')
putIf(isAdmin, 'admin', true)
put('profile.id', 1)