A self-hosted PWA that keeps your loyalty cards on your phone. Add cards by scanning the barcode with the camera (or typing the number), and they are shown as tappable tiles with the barcode ready to scan at checkout.
- Frontend: vanilla JS + Vite, installable as a PWA on iOS/Android
- Backend: small Express server
- Data: single
cards.jsonfile stored in a Docker volume (single user, no auth)
npm install
npm run build
npm start # serves http://localhost:3000For development with hot reload:
npm run dev # frontend on :5173, proxies /api to :3000
# in another terminal:
npm startData is stored in ./data/cards.json by default. Override with DATA_DIR or PORT:
DATA_DIR=/var/lib/loyalty PORT=8080 npm startCamera scanning requires a secure context (HTTPS or
localhost). On a phone, open the app overhttps://— otherwise use manual entry.
docker compose up -d --buildThe app is available at http://<server-ip>:8080.
docker compose up -d --build docker compose down # stop, keep data volume
docker compose down -v # stop and delete stored cards| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Port the server listens on |
DATA_DIR |
/data |
Directory containing cards.json |
The compose file maps host port 8080 to the container and mounts the named volume loyalty-cards-data at /data, so your cards survive container rebuilds.
- Open the app in Safari/Chrome
- iOS: tap Share → Add to Home Screen → Add
- Android: tap the browser menu → Add to Home screen / Install app
Open it from the home screen like a native app.
- Cards are stored as
cards.jsonin the volume — back it up alongside your other data. - Single-user: the app assumes only you use it and adds no authentication. Expose it on a trusted network only.
- Barcode formats: QR, EAN-8/13, UPC-A/E, Code 128/39/93, ITF, Codabar.