← back to projects
mobilefullstackbackendapilive

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.

GitHub ↗

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

FlutterDartRiverpodGoRouterHiveNode.jsExpressTypeScriptPostgreSQLStripeFirebase Cloud MessagingDockerJWT

API Showcase

POST/api/v1/auth/register

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 } }
POST/api/v1/ordersauth

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": [] }
POST/api/v1/payments/intentauth

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" }
POST/api/v1/payments/webhook

Stripe webhook handler — verifies signature, transitions order to 'confirmed' on payment_intent.succeeded or 'cancelled' on payment_intent.payment_failed.

{ "received": true }
GET/api/v1/products/search

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

Platform

Docker / Railway

Docker

Yes