EarthVoice Backend is a small Node.js + Express service that powers the EarthVoice project: poetic, first-person narratives for places. It uses Google Gemini for text generation and Backboard for optional persistent assistant memory. The backend exposes HTTP endpoints and a WebSocket presence server.
- Generate narrated descriptions for predefined locations using Gemini.
- Chat-style assistant replies (Backboard-backed conversation threads; Gemini fallback).
- In-memory short-term memory per location and optional persistent memory sync with Backboard.
- WebSocket presence server for real-time presence (cursor/heartbeat events).
Requirements
- Node.js 18+ (tested on Node 20)
- npm
Install
npm installEnvironment variables
Create a .env file in the project root with the following keys:
GEMINI_API_KEY— Google GenAI / Gemini API key (required for Gemini features)BACKBOARD_API_KEY— Backboard API key (optional; when missing or if Backboard fails the app falls back to Gemini)PORT— optional, defaults to5000in this project
Example .env:
GEMINI_API_KEY=your_gemini_key_here
BACKBOARD_API_KEY=your_backboard_key_here
PORT=5000Run
# development (uses nodemon)
npm run dev
# production
npm startThe server will log:
WebSocket presence server running on /ws
🌍 EarthVoice backend running on http://localhost:5000
Base URL: http://localhost:${process.env.PORT || 5000}
-
GET
/— health and endpoint list -
GET
/api/locations— list of supported locations -
POST
/api/narrative— Generate a narrative for a location- Body:
{ "locationId": "<id>" } - Response:
{ reply: "..." }
- Body:
-
POST
/api/chat— Send a chat message to the assistant for a location- Body:
{ "locationId": "<id>", "message": "Hello", "history": [ ...optional conversation...] } - Response:
{ reply: "..." } - Behavior: Tries Backboard (persistent assistant + thread) first. If Backboard fails, falls back to Gemini.
- Body:
-
GET
/api/memory/:locationId— Get recent in-memory memories for a location -
POST
/api/memory— Add a short memory- Body:
{ "locationId": "<id>", "visitor": "Name (optional)", "snippet": "Short note..." }
- Body:
WebSocket presence
- Connect to
ws://localhost:<PORT>/wsand send a JSON message{ type: "hello", id: "some-unique-id" }to register. The server broadcastscount,leave, andcursormessages.
- Main entry:
src/index.js - Routes:
src/routes/api.js - Backboard integration:
src/services/backboard.js - Gemini/GenAI integration:
src/services/gemini.js - In-memory short-term memory:
src/services/memoryStore.js - WebSocket presence:
src/services/presence.js
If you change Backboard calls, follow the SDK shapes in node_modules/backboard-sdk/dist/client.js (createAssistant expects an object { name, description, system_prompt }; addMemory expects { content, metadata }).
Feel free to open issues or submit pull requests. Small improvements to add:
- Persistence for the in-memory memory store (DB)
- Retry/backoff for Backboard calls
- Unit tests for route handlers
MIT