Drooli: Dog Food Recipe App & CMS for Dog's Lounge
Sole developer: app, backend, CMS, and delivery (JJKSoftware)
- Flutter
- Ruby on Rails
- PostgreSQL
- Shopify
- CMS
- Sole Developer

TL;DR
Drooli is a fresh, balanced homemade dog food app I built as sole developer for Dog's Lounge, a Shopify retailer, live on Google Play and the App Store. It is a commercial product delivered end-to-end under my JJKSoftware banner: the client relationship, a Flutter app of roughly 24k lines of Dart across 26 feature modules, a Ruby on Rails API backend with a variant-based nutrition data model, the releases, and the CMS that lets the retailer manage its own recipe content without me in the loop.
The brief
Dog's Lounge sells through a standard Shopify storefront, but their real product is guidance: helping owners feed their dogs fresh, balanced, homemade food. That needed a home a web catalog can't provide. Recipe content filtered by each dog's actual needs, a persistent place in the customer's pocket, and a connection back to the store they already run.
The app
A cross-platform Flutter app shipped to both stores from one codebase, built to a strict Clean Architecture enforced per feature: a pure-Dart domain layer (models, use cases, repository interfaces, no Flutter imports), a network layer of DTOs and mappers, concrete repositories, and a Riverpod UI layer of StateNotifier controllers with Equatable state. Every feature wires its own providers in a module file, and every operation is its own use case class behind a shared interface. Users manage dog profiles, browse a recipe and ingredient catalog filtered by each dog's health needs, and shop supplement products through the embedded Shopify storefront.
The engineering under the surface is where the app earns its keep:
- Auth and token lifecycle. A Dio interceptor injects auth headers, proactively refreshes tokens seven days before expiry, and on an expired or invalid token logs the user out and redirects to login through a global navigator key, no BuildContext required.
- Two-tier encrypted persistence. Auth tokens live in the OS keychain via secure storage; user, dog, and shopping-list data live in AES-encrypted Hive boxes. The split is deliberate, chosen per data sensitivity and access pattern.
- Stale-while-revalidate caching. Recipes and products render instantly from a local cache while silently revalidating from the network, with the cache scoped per content type and handling for failed image loads.
- Pagination with race handling. Recipe lists load incrementally, and in-flight page requests are discarded if the active filter changes before the response returns.
- Shopify checkout renders in a WebView with locally stored, validated checkout tokens, driven by the Shopify storefront packages.
- Operations. Firebase (analytics, Crashlytics) plus Sentry for error and performance monitoring. Analytics consent is explicitly reset on logout or when a consent fetch fails: privacy-conscious by default. Three environments (demo, test, prod) selected at build time, with secrets injected from a gitignored file and accessed only through a single config module.
The backend
A Rails 7.1 API on PostgreSQL, deployed on Heroku with New Relic monitoring, tested with RSpec and FactoryBot (98 spec files across controllers and services). The core entities are Dog, Recipe, Ingredient, and Nutrient, wired together through a dense set of many-to-many relationships:
- Dogs carry breed (250+ enumerated breeds), sex, body condition, activity level, and maturity, used to calculate resting and daily energy requirements for personalised feeding recommendations.
- Recipes are filterable by protein, cooking method, grain type, fat content, legume type, life stage, and targeted nutrition, each its own join model composed into query scopes.
- Recipe variants support different activity-level and supplement combinations, each with its own ingredient list and nutrient breakdown, with one variant designated the base.
The API itself is namespaced and versioned, one serializer per resource, with shared auth and error handling in a base controller and opt-in pagination added carefully after a rate-limiting incident. Image handling is deliberately security-conscious: ActiveStorage URLs expire in production, Rack::Attack throttles the image endpoints specifically, and every attachment is validated for content type and size.
The ADF import pipeline
The standout backend feature. Recipe nutrition data originates as ADF nutrition-lab export files (CSV and XLSX), and the CMS imports them directly: the pipeline parses a lab file and bulk-loads nutrient, macro-nutrient, and ingredient data into a recipe variant inside a single transaction. It handles two upload variants (with and without supplements) that map onto different columns, reorders nutrients deterministically to match the admin's expected display order, and does fuzzy ingredient matching backed by a learned alias system. Unmatched ingredient names are never silently auto-created; they surface in an admin resolver UI where a human confirms the match once, and it is remembered. Bulk-importing a recipe library becomes an upload plus a short review, not a data-entry project.
The CMS
A server-rendered admin dashboard (Turbo/Stimulus), separate from the JSON API, where Dog's Lounge manages recipes and ingredients directly, with inline preview-before-import for lab files and per-variant editing. For a sole-developer commercial project this is the difference between delivering a product and becoming a permanent dependency: the client runs their own content; I ship features.
Shopify integration
Both ends of the stack talk to Shopify. The backend wraps the GraphQL Storefront API with a full customer-auth flow (login, account creation, token refresh), a cart and checkout service, product sync, and a dedicated error mapper that translates Shopify's error shapes into clean API responses. The app consumes that alongside the storefront catalog and completes checkout in an embedded WebView.
Results
- Live on both the App Store and Google Play for a paying commercial client
- Full-stack sole-developer delivery: Flutter app, Rails API, CMS, releases, and support under JJKSoftware
- Client self-serve content management, including direct import of lab nutrition files with admin-in-the-loop conflict resolution
- Iterative production history: ~400 backend commits, with the import pipeline evolving from CSV-only to CSV+XLSX with editable variant tabs, plus pagination and image-security hardening along the way