310 lines
6.7 KiB
Markdown
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.
|