Ton app aurait pu être une page web. Celle-ci l'est.
Murmure est un journal vocal one-page : appuie sur le micro, parle, la transcription se fait dans ton navigateur (Web Speech API) et ta note est sauvegardée. Zéro installation, zéro compte, zéro tracking, zéro cookie.
Le backend minimal (Express + SQLite) sert la page et stocke tes notes.
node:sqlite étant natif dans Node 22.5+, il n'y a aucune dépendance
native à compiler : le déploiement se fait sans accrocs.
- 🎙️ Journal vocal — transcription temps réel en français, 100% locale
- 🕶️ Privacy-first — ta voix ne quitte jamais ton navigateur
- ☁️ Sync automatique — les notes partent au backend quand il est joignable, sinon stockage local (repli localStorage)
- 🗑️ CRUD complet — créer, lister, supprimer ses notes
- 📄 Export Markdown — télécharge toutes tes notes en
.mden un clic - ✨ Résumé IA — synthèse de tes murmures (optionnel, via
OPENAI_API_KEY) - 🎭 Mode démo — charge des notes d'exemple pour voir le rendu
- 📱 Responsive — aussi belle sur téléphone que sur écran 4K
- 🏷️ Aucune dépendance front — HTML + CSS + JS vanilla
Frontend → Vercel (proxy /api/* vers le backend) :
https://murmure-rho.vercel.app
Backend → Railway (SQLite persistante, volume /data) :
https://murmure-api-production-837c.up.railway.app
┌──────────────────┐ ┌──────────────────────────┐
│ Vercel (Edge) │ │ Railway (Docker) │
│ │ │ │
│ index.html │ ──► │ Express + SQLite │
│ (statique) │ /api/* volume /data │
│ │ │ │
│ https:// │ │ https:// │
│ murmure-rho │ │ murmure-api-production │
│ .vercel.app │ │ -837c.up.railway.app │
└──────────────────┘ └──────────────────────────┘
Chaque visiteur peut utiliser l'app en local (localStorage, aucun serveur nécessaire) ou en cloud (les notes sont persistées en base via le proxy Vercel → Railway).
# 1. installer les dépendances (une seule : express)
npm install
# 2. lancer
npm start→ Ouvre http://localhost:8080
npm run dev relance automatiquement le serveur à chaque modification.
docker build -t murmure .
docker run -d --name murmure -p 8080:8080 \
-v murmure-data:/data \
murmureLa base SQLite est persistée dans le volume murmure-data.
# docker-compose.yml
services:
murmure:
build: .
ports:
- "8080:8080"
volumes:
- murmure-data:/data
restart: unless-stopped
volumes:
murmure-data:docker compose up -d --buildrailway init -n murmure
railway add -s murmure-api # service Docker
railway volume add -m /data # volume persistant
railway volume attach <volume> <service>
railway variable set DATA_DIR=/data
railway up -d -ydocker build -t murmure .
docker run -d --name murmure -p 8080:8080 \
-v murmure-data:/data \
-e DATA_DIR=/data \
murmure{
"outputDirectory": "public",
"rewrites": [
{ "source": "/api/(.*)", "destination": "https://<railway-url>/api/$1" },
{ "source": "/(.*)", "destination": "/index.html" }
]
}Ajoute OPENAI_API_KEY (ou OPENAI_BASE_URL + OPENAI_MODEL)
dans les variables d'environnement du service Railway.
Le bouton ✨ Résumer apparaît alors automatiquement dans l'interface.
| Variable | Défaut | Description |
|---|---|---|
PORT |
8080 |
Port d'écoute |
HOST |
0.0.0.0 |
Interface d'écoute |
DATA_DIR |
./data |
Dossier de la base SQLite |
OPENAI_API_KEY |
— | Active le résumé IA ✨ |
OPENAI_BASE_URL |
https://api.openai.com/v1 |
API compatible OpenAI |
OPENAI_MODEL |
gpt-4o-mini |
Modèle utilisé pour le résumé |
npm testTests d'intégration (node:test, zéro dépendance) : health check, CRUD,
validation des entrées, service de la page.
murmure/
├── server/
│ ├── index.js # serveur Express + API REST
│ └── db.js # base SQLite (node:sqlite natif)
├── public/
│ └── index.html # la page unique (HTML + CSS + JS inlinés)
├── test/
│ └── api.test.js # tests d'intégration
├── Dockerfile # build multi-stage
└── .env.example # variables d'environnement
💡 Tout est dans une seule page. Le frontend est un unique
index.htmlautonome (CSS + JS inlinés) : il fonctionne aussi ouvert directement depuis le disque, sans serveur. Le backend Express le sert et stocke les notes.
| Méthode | Route | Description |
|---|---|---|
GET |
/api/health |
Health check ({ ok: true, uptime, version }) |
GET |
/api/config |
Config exposée au front ({ ai, version }) |
GET |
/api/notes |
Liste des notes (plus récentes d'abord) |
POST |
/api/notes |
Créer une note { "text": "..." } |
DELETE |
/api/notes/:id |
Supprimer une note |
POST |
/api/summarize |
Résumé IA des notes (501 si non configuré) |
Parce qu'une app de prise de notes ne mérite pas 200 Mo, un compte, des permissions, des notifications et 12 mises à jour par semaine. Une page web charge en une seconde, fonctionne partout, et ne demande rien en échange.
"Your app could have been a webpage" — Hacker News, 1 034 points