Van WebDAV naar REST API: een volwassen sync-architectuur voor de weegschaal-app
De afgelopen dagen heb ik de synchronisatie van mijn weegschaal-app volledig herzien. De app slaat metingen lokaal op in SQLite en synchroniseert ze met een centrale server. Tot nu toe gebeurde dat via WebDAV naar Nextcloud — een simpel JSON-bestand dat telkens in zijn geheel werd gedownload, samengevoegd en teruggeüpload. Functioneel, maar niet ideaal.
Waarom WebDAV niet voldeed
Het oude sync-mechanisme was primitief: bij elke sync werd het volledige measurements.json bestand van de WebDAV-server gehaald, lokaal gemerged op basis van updatedAt, en daarna in zijn geheel teruggeschreven. Met 1300+ metingen werd dat bestand steeds groter, en elke sync — ook als er maar één meting was veranderd — verstuurde het hele bestand over de lijn. Bovendien leverde Basic Auth via WebDAV geen fijnmazige controle over wie toegang had, en was er geen logging van foutieve inlogpogingen.
De nieuwe architectuur
De oplossing is een eigen REST API, gehost op een subdomein van mijn domein, met een MariaDB-backend en Bearer token-authenticatie. De stack:
- Server: Fedora Linux, nginx, PHP 8.5 (Slim Framework 4), MariaDB
- Client: React Native (Expo), expo-sqlite, expo-secure-store
- Authenticatie: Bearer token, handmatig aangemaakt via SQL
- Sync: incrementeel via
?since=timestamps — last-writer-wins
Endpoints
De API kent drie endpoints, allemaal beschermd met Bearer token-validatie:
| Methode | Pad | Werking |
|---|---|---|
GET | /api/v1/measurements?since=ISO | Delta-sync: alleen metingen met updated_at > since |
POST | /api/v1/measurements | Upsert op client-ID — de server accepteert het client-gegenereerde UUID |
DELETE | /api/v1/measurements/:id | Soft delete: zet deleted=1 en update updated_at |
Geen PUT, geen register/token-endpoints. Minimaal en doelgericht.
Incrementele sync
De sync-flow is nu gebaseerd op timestamps, zonder syncStatus enum. Het mechanisme is hetzelfde als wat CouchDB, PouchDB en tal van productie-apps gebruiken:
- Delta ophalen:
GET ?since=lastSyncAt→ alleen gewijzigde records van de server - Mergen: last-writer-wins op
updatedAt. Remote records metdeleted=trueoverschrijven lokaal - Pushen: alle lokale records met
updatedAt > lastSyncAtworden naar de server gestuurd - Verwijderde records: lokaal een
deletedAttimestamp gezet, bij volgende sync viaDELETEnaar de server - High-water mark:
lastSyncAtwordt bijgewerkt naar de huidige tijd
Bij een netwerkfout blijft een meting gewoon lokaal staan — de volgende sync pakt hem automatisch op omdat updatedAt > lastSyncAt.
Fail2ban-vriendelijke logging
Een API die op internet staat trekt vroeg of laat ongenode gasten aan. Om hierop voorbereid te zijn logt de API elke mislukte authenticatiepoging naar /var/log/weegschaal-api/auth.log in een formaat dat fail2ban direct kan verwerken:
[2026-07-20 18:06:07] 401 94.157.121.179 GET /api/v1/measurements - invalid Bearer token
[2026-07-20 18:06:07] 404 94.157.121.179 GET /.env - unknown route
De fail2ban jail grijpt in bij 5 foutieve pogingen binnen 5 minuten en bant het IP een uur lang. Ook 404’s op niet-bestaande routes worden gelogd — een klassiek signaal van fuzzing-tools.
De logrotering (12 weken, wekelijks) en SELinux-context (httpd_log_t) zijn uiteraard geregeld.
Resultaten delen
Een onverwacht leuke toevoeging: de share-knop op het dashboard. Rechtsboven in de kaart met je laatste en voorlaatste meting zit een deel-icoon. Tik erop, en de hele kaart wordt als PNG-afbeelding geëxporteerd. Je kunt hem direct delen via WhatsApp, Signal, e-mail — wat je maar wilt. De techniek erachter: react-native-view-shot vangt de view, expo-sharing opent het native deelmenu.
Wat nog meer is veranderd
- App-icoon: vernieuwd met een eigen afbeelding
- Dashboard: toont naast de laatste meting nu ook de voorlaatste en het gewichtsverschil
- Laad-indicator: geen verwarrende “Nog geen metingen” meer tijdens het laden — het dashboard toont nu “Laden…”
- Database-migratie: een
ALTER TABLEzorgt dat bestaande SQLite-databases de nieuwedeletedAt-kolom krijgen zonder dataverlies
Conclusie
Wat begon als een simpel sync-scriptje via WebDAV is uitgegroeid tot een volwaardige REST API met productie-grade logging, fail2ban-integratie, en een UX die het delen van meetresultaten net zo makkelijk maakt als een screenshot. De app doet nu wat hij moet doen: metingen opslaan, synchroniseren, en — sinds kort — met één tik delen met wie je maar wilt.