# Braindrops Backend Self-hosted Bun backend server for the Braindrops drops system. ## Features - **REST API** - Automatic endpoint loading from drop backends - **WebSocket** - Real-time bidirectional communication - **SQLite Database** - Lightweight, file-based storage - **Auto-Discovery** - Automatically loads drop backends on startup - **Hot Reload** - Automatically reloads when code changes (dev mode) - **CORS Support** - Configured for local development and production ## Architecture ``` backend/ ├── src/ │ ├── index.js # Main server entry point │ ├── db.js # SQLite database utilities │ ├── loader.js # Drop backend auto-discovery │ ├── router.js # API routing │ ├── websocket.js # WebSocket handling │ └── shared/ │ └── middleware.js # CORS and other middleware ├── db/ │ └── braindrops.db # SQLite database (gitignored) └── package.json ``` ## Development ### Starting the Backend The backend is automatically started when you run: ```bash npm run dev:drops ``` This starts: - Drop dev server on port 3000 - CMS on port 8787 - Backend on port 3001 (only loads the specified drop's backend) ### Standalone Backend To run the backend independently: ```bash npm run dev:backend ``` This loads ALL drop backends at once. ## Drop Backend Structure Each drop can optionally have a `backend/` folder: ``` drops// ├── src/ # Frontend code ├── backend/ # Backend code (optional) │ ├── api.js # REST endpoints │ ├── schema.sql # Database schema │ └── websocket.js # WebSocket handler (optional) └── public/ ``` ### Example API (api.js) ```javascript import { dbHelpers } from '../../../backend/src/db.js' const { query, queryOne, run } = dbHelpers export const routes = { 'GET /items': async (req) => { const items = query('SELECT * FROM mydrop_items') return Response.json({ items }) }, 'POST /items': async (req) => { const body = await req.json() const result = run('INSERT INTO mydrop_items (data) VALUES (?)', [body.data]) return Response.json({ id: result.lastInsertRowid }) } } ``` ### Example Schema (schema.sql) ```sql CREATE TABLE IF NOT EXISTS mydrop_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); ``` ### Example WebSocket (websocket.js) ```javascript export const websocket = { open(ws) { ws.send(JSON.stringify({ type: 'connected' })) }, message(ws, message) { const data = JSON.parse(message) // Broadcast to all clients ws.publish('mydrop', JSON.stringify(data)) } } ``` ## API Endpoints ### Health Check ``` GET /health ``` Returns server status and loaded drops. ### Drop APIs ``` /api//* ``` Each drop's API is namespaced under `/api//`. Example: ```bash # Get melodies from image-melody drop curl http://localhost:3001/api/image-melody/melodies # Create a new melody curl -X POST http://localhost:3001/api/image-melody/melodies \ -H "Content-Type: application/json" \ -d '{"name":"My Melody","imageData":{},"melodyData":{}}' ``` ### WebSocket ``` ws://:/ws/ ``` Example: ```javascript const ws = new WebSocket('ws://localhost:3001/ws/image-melody') ws.onmessage = (event) => { const data = JSON.parse(event.data) console.log('Received:', data) } ws.send(JSON.stringify({ type: 'hello' })) ``` ## Database The backend uses SQLite for data storage: - Database file: `backend/db/braindrops.db` - Tables use naming convention: `_` - Schemas are auto-applied on startup - WAL mode enabled for better concurrency ### Database Helpers Available in drop backends via import: ```javascript import { dbHelpers } from '../../../backend/src/db.js' const { query, queryOne, run } = dbHelpers // Get all rows const items = query('SELECT * FROM mydrop_items') // Get single row const item = queryOne('SELECT * FROM mydrop_items WHERE id = ?', [1]) // Insert/Update/Delete const result = run('INSERT INTO mydrop_items (data) VALUES (?)', ['test']) console.log(result.lastInsertRowid) // Get inserted ID ``` ## Environment Variables Create a `.env` file in the root directory: ```bash # Production URL for CORS PRODUCTION_URL=https://yourdomain.com # Optional: Custom port PORT=3001 # Your API keys OPENAI_API_KEY=your-key SOME_SERVICE_KEY=your-key ``` Access in code: ```javascript const apiKey = process.env.OPENAI_API_KEY ``` ## Production Build Build for production: ```bash node bin/buildBackend.js ``` This creates `backend/dist/` with: - All source files - package.json - Database directory - Start script ### Deploying to Production 1. Copy `backend/dist/` to your server 2. Install dependencies: `cd backend/dist && bun install` 3. Set environment variables 4. Start: `./start.sh` ### nginx Configuration ```nginx # API proxy location /api/ { proxy_pass http://localhost:3001; } # WebSocket proxy location /ws/ { proxy_pass http://localhost:3001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } ``` ## Troubleshooting ### Backend not starting 1. Check if port 3001 is available: ```bash lsof -i :3001 ``` 2. Check backend logs in the terminal 3. Ensure Bun is installed: ```bash bun --version ``` ### Drop backend not loading 1. Check that `drops//backend/` exists 2. Check that `api.js` has a valid `export const routes = {}` 3. Look for errors in terminal output during startup ### Database errors 1. Check that `backend/db/` directory exists 2. Check file permissions 3. Review schema.sql for syntax errors 4. Delete `braindrops.db` to start fresh (will lose data) ## Development Tips 1. **Route Patterns** - Use Express-style patterns: - `GET /items` - List all - `GET /items/:id` - Get by ID - `POST /items` - Create - `PUT /items/:id` - Update - `DELETE /items/:id` - Delete 2. **Error Handling** - Return proper status codes: ```javascript if (!item) { return Response.json({ error: 'Not found' }, { status: 404 }) } ``` 3. **Validation** - Always validate input: ```javascript if (!body.name || !body.data) { return Response.json({ error: 'Missing fields' }, { status: 400 }) } ``` 4. **JSON Storage** - Store complex data as JSON: ```javascript run('INSERT INTO items (data) VALUES (?)', [JSON.stringify(obj)]) ``` 5. **WebSocket Channels** - Use drop name for pub/sub: ```javascript ws.publish('mydrop', JSON.stringify({ event: 'update' })) ``` ## License Same as parent project.