braindrops.nicwands.com/backend/README.md
2026-07-23 15:27:12 -04:00

310 lines
6.7 KiB
Markdown

# 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 <drop-name>
```
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/<drop-name>/
├── 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/<drop-name>/*
```
Each drop's API is namespaced under `/api/<drop-name>/`.
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://<host>:<port>/ws/<drop-name>
```
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: `<drop-name>_<table-name>`
- 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/<drop-name>/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.