All case studies
FloretConsumer Mobile · Health & Wellbeing2026

An Offline-First Consumption & Inventory Tracker

A discreet, offline-first mobile app for tracking consumption and managing inventory — pairing a resilient Expo / React Native client with a Symfony + MongoDB API, run-out prediction, mood tracking and Stripe subscriptions.

React NativeExpoOffline-FirstSymfonyMongoDBStripe
Offline-first
Local-write, sync-later
Expo + Symfony
RN client · PHP API
PSR-12 · PHPStan
Parallel Pest suite

Overview

Floret is a discreet, mobile-first application for tracking consumption, managing personal inventory and understanding usage over time. It is built for a privacy-sensitive audience: the product deliberately feels like a well-kept garden journal, not a pharmacy dashboard — warm, precise and unobtrusive.

The platform pairs a cross-platform React Native (Expo) application with a Symfony API backed by MongoDB, and is delivered as a freemium product with subscription billing via Stripe. The brief was to build a polished consumer app that works reliably with intermittent connectivity, keeps sensitive data secure, and turns raw consumption logs into genuinely useful insight — run-out predictions, wellbeing correlation and usage analytics.

The Challenge

A consumption tracker only earns daily use if logging is instant and dependable. That set the core constraints:

  • Offline-first. A log must always succeed locally and reconcile with the server later, without data loss or duplication.
  • Discretion and security. Credentials and tokens must be stored securely on-device, and the interface must stay calm and private.
  • Insight, not just records. The product's value is in presentation and analysis — clean data, run-out forecasting, and optional before/after mood tracking.
  • A real strain catalogue. Users expect to search a large library rather than type free text, without the cost of maintaining that dataset in-house.
  • Sustainable monetisation. A freemium model with a clean paywall and robust subscription lifecycle handling.

The Solution

Floret is a two-tier product: a thin, resilient mobile client over a stateless JSON API, with an on-device database acting as the source of truth for the user until the server confirms a write. The client never blocks on the network — writes are committed to a local SQLite database and enqueued; a background process replays the queue against the API when connectivity returns.

floret-app

Expo SDK 56 / React Native 0.85 / React 19 client with expo-router navigation, TanStack Query and Zustand — thin screens over a typed API and offline engine.

floret-api

Symfony 7.4 / PHP 8.2+ API layered controller → service → repository over a MongoDB document store, with JWT auth, rate limiting and scheduled work.

The Mobile Application

Built with Expo SDK 56 / React Native 0.85 / React 19 using expo-router file-based navigation.

  • State & data — TanStack Query manages all server state (caching, background refetch, optimistic updates); Zustand holds lightweight client state across focused stores (auth, offline, theme, presets). A typed API layer wraps every resource behind an Axios client with automatic JWT injection and 401/403 session-recovery interceptors.
  • Security & platform — expo-secure-store keeps access tokens in the device keystore/keychain; expo-notifications delivers reminders; EAS manages native builds and release channels.
  • Interface — “Botanical Calm” — a deliberate design system: a single moss-green brand family, ink-toned data, a warm paper canvas, and semantic supply colours (leaf → amber → clay) that shift as stock runs low. Typeset in Fraunces (display) and Inter (body), with gifted-charts analytics and reanimated motion.

The Offline-First Engine

The heart of the app's reliability. Every write is committed locally first, then reconciled:

  • expo-sqlite provides the local database — the user's source of truth until the server confirms.
  • A durable sync queue records every create/update/delete with its payload, retry count and last error; the queue drains in order once online.
  • NetInfo drives connectivity state and a useOfflineSync hook orchestrates reconciliation — so the UI reflects pending, syncing and synced states honestly, with no data loss or duplication.

The API & Domain Model

A Symfony 7.4 / PHP 8.2+ application following clean controller → service → repository layering over MongoDB (Doctrine MongoDB ODM). Core documents: User / UserPreferences, Entry (a consumption event — method, amount, unit, notes, timestamp), Inventory, Strain / StrainSnapshot, MoodAssessment and Reminder.

Embedding an immutable StrainSnapshot into each entry captures strain details as they were at the time of logging, so historical records stay accurate even if the catalogue entry later changes.

Run-out prediction

A weighted average of recent daily usage (14-day window) projects a run-out date with an honest confidence rating (no_data → low → medium → high) based on available data points.

Strain catalogue

Cache-first search served from MongoDB, falling back to the Weedmaps API (with a strict timeout) only when short of results, persisting what it fetches. A scheduled command keeps the cache warm.

Subscriptions & billing

Stripe integration for the freemium upgrade path, with a dedicated webhook controller handling payment, renewal and cancellation events idempotently.

Scheduled work

Symfony Scheduler and Messenger drive background jobs — dispatching due reminders and running strain sync on a cadence.

Mood correlation

Optional before/after wellbeing capture, surfacing patterns between consumption and how users feel.

Analytics & dashboard

Aggregation of entries into usage statistics powering the app's charts and insight surfaces.

Security & Cross-Cutting Concerns

  • Authentication — stateless JWT via LexikJWTAuthenticationBundle, with a custom user provider and validators enforcing identity and input integrity.
  • Rate limiting — Symfony Rate Limiter protects auth and write endpoints.
  • CORS — Nelmio CORS for controlled cross-origin access from the app.
  • Email & documents — Symfony Mailer for transactional mail; KnpSnappy available for PDF generation.

Engineering Standards

Quality gates are first-class across both codebases:

  • API — PHP 8.2+ on Symfony 7.4; a Pest 4 suite run in parallel with coverage; PHPStan static analysis, Rector refactoring and PHP_CodeSniffer (PSR-12) bundled into a single lint gate; Dockerised with a scripted deploy.
  • App — TypeScript throughout with ESLint, and strict separation of concerns (API layer, stores, hooks, DB and UI cleanly isolated) that keeps the offline logic testable and the screens thin.

Technology Stack

MobileReact Native 0.85, Expo SDK 56, React 19, expo-router, TypeScript
App stateTanStack Query (server state), Zustand (client state)
Offlineexpo-sqlite, custom sync queue, NetInfo
Deviceexpo-secure-store (tokens), expo-notifications, EAS builds
App UIFraunces + Inter, react-native-gifted-charts, reanimated/worklets, expo-glass-effect
API frameworkSymfony 7.4, PHP 8.2+
DataMongoDB (Doctrine MongoDB ODM)
AuthJWT (Lexik), custom UserProvider, rate limiting
Async / scheduledSymfony Messenger, Symfony Scheduler
BillingStripe (checkout + webhooks)
IntegrationsWeedmaps strain API (cached), Symfony Mailer, KnpSnappy (PDF)
InfrastructureDocker, Docker Compose, scripted deploy
QualityPest (parallel), PHPStan, Rector, PHP_CodeSniffer (PSR-12), ESLint

Outcomes

A genuinely offline-first experience — every log succeeds instantly and reconciles safely, dependable wherever it is used.
Secure handling of sensitive data, with tokens in the device keystore and a calm, discreet interface built on a coherent design system.
Actionable insight rather than raw logs — run-out forecasting with honest confidence levels, usage analytics and optional mood correlation.
A rich strain catalogue delivered cost-effectively through cache-first external enrichment.
A complete freemium business model with a clean paywall and resilient Stripe subscription lifecycle handling.
A cleanly layered, well-tested codebase on current framework versions, positioned for straightforward extension.

Building a mobile product?

Offline-first apps, resilient sync, and clean API design are our core work. Let's talk.

Get in Touch