Signature Scent
A full-stack premium e-commerce mobile app for a local perfume boutique, built with Flutter (iOS & Android) and a Node.js/Express REST API.
The Problem
A local perfume boutique needed a branded mobile shopping experience — customers had no way to browse the catalog, save favourites, or place orders outside the physical store. The app needed to work offline and feel as premium as the products it sells.
My Approach
Built a strict 3-layer Flutter architecture (Presentation → Domain ← Data) so the UI and business logic are completely decoupled from the backend. All remote calls go through repository abstractions backed by Dio, with Hive caching for offline support. The backend is a stateless Node.js/Express TypeScript API with PostgreSQL, JWT auth, and Stripe for payments — containerised with Docker and designed to be swapped without touching the Flutter layers.
Challenges & Solutions
Keeping the offline-first experience consistent while syncing wishlist and cart state with the server required careful cache invalidation logic. Integrating Stripe's payment sheet with server-side amount calculation (never trusting the client) and then polling for webhook-confirmed order status added async complexity to the checkout flow. Maintaining a Belle Époque visual identity with custom filigree widgets and ornate typography across both iOS and Android without platform-specific hacks was also non-trivial.
Results & Impact
Complete end-to-end shopping flow: browse → wishlist → cart → Stripe checkout → order tracking with FCM push notifications. 26 correctness properties validated with property-based tests. 90%+ domain layer test coverage.
Architecture Overview
Flutter frontend with a strict 3-layer architecture (Presentation → Domain ← Data) communicates with a stateless Node.js/Express REST API backed by PostgreSQL; Stripe handles payments via server-side Payment Intents and webhooks, and Firebase Admin SDK dispatches push notifications.
Tech Stack
API Showcase
Register a new user — hashes password with bcrypt (cost 12), returns a signed 7-day JWT.
{ "token": "eyJ...", "user": { "id": "uuid", "email": "[email]", "emailVerified": false } }Create an order — validates all items are in stock, calculates total server-side, persists order with status 'pending'.
{ "id": "uuid", "status": "pending", "total": 149.00, "deliveryMethod": "localDelivery", "items": [] }Create a Stripe PaymentIntent — amount calculated from DB order items, never from client input. Returns client_secret for the Flutter payment sheet.
{ "clientSecret": "pi_xxx_secret_xxx", "paymentIntentId": "pi_xxx" }Stripe webhook handler — verifies signature, transitions order to 'confirmed' on payment_intent.succeeded or 'cancelled' on payment_intent.payment_failed.
{ "received": true }Search and filter the perfume catalog by keyword, fragrance family, price range, and availability.
{ "products": [{ "id": "uuid", "name": "Oud Royale", "price": 149.00, "inStock": true, "fragranceFamily": "oriental" }] }Deployment
Docker / Railway
Yes