Delivered remotely for a business based in Dubai, United Arab Emirates. Names withheld by agreement.
Project Overview
Industry: Hospitality and consumer loyalty — paid membership discount schemes for local dining venues.
Type of solution: A consumer subscription product: a native iOS application, a token-authenticated REST API, and a web-based administrative back office.
Business context: Discount club schemes have traditionally been sold as a physical card and a printed directory of participating venues. That model is structurally limited. The venue list ages immediately. The member has no way to find which participating venues are near them at the moment they are deciding where to eat. And the operator, having taken a recurring payment, has almost no visibility of who holds a live membership or how the scheme is being used. Rebuilding the scheme as a mobile membership addresses all three at once.
General users: Consumer members using the mobile application, and platform administrators managing venues, members and content through the back office.
General purpose: To replace a printed card and directory with a live, location-aware membership that members carry on their phone — and to give the operator a single administrable record of venues, members, membership status and content.
Scope note: This engagement delivered the first release of the platform — the account, membership, discovery, review and administration foundation, together with the deployment pipeline that subsequent releases build on. It is presented here at that scope.
The Business Challenge
A printed directory is stale on the day it ships. Venues join and leave a scheme continuously. Any medium that cannot be updated after distribution guarantees that members will occasionally arrive somewhere that is no longer participating — which is the fastest way to lose a subscriber.
Members decide where to eat by location, not by list. The question a member actually asks is "what is near me right now?". A directory sorted alphabetically cannot answer it. Proximity has to be a first-class property of the data, computed and ordered by the platform.
A plastic card proves nothing useful. It shows the member has a card. It does not show whether the membership is current, and it gives the operator nothing to work with. Membership status needs to be a server-held record the application reads, not a physical object.
Legal and policy content changes faster than app releases. Terms, privacy policy and scheme rules change on the operator's schedule, not the App Store's. Baking that text into the binary means a release cycle every time a clause moves.
Growth in this category is word-of-mouth. Members recommend a discount scheme to people they eat with. If the platform cannot attribute a recommendation, the operator cannot reward it — and loses the cheapest acquisition channel available.
Venue content is heavy. Every venue carries multiple photographs, and members browse them on mobile connections. Naive image handling makes the product feel slow at exactly the moment it needs to feel effortless.
Two environments, from the first week. A product with a paying membership needs a staging environment that genuinely mirrors production, and a way to promote changes to both without manual server work.
Our Approach
Make proximity a property of the query, not the client. Rather than shipping the full venue list to the device and sorting it there, the API computes great-circle distance from the member's supplied position directly in the query and returns it as a field on every venue. The client receives data that is already ordered by relevance, and the same endpoint serves both the list view and the map without a second call.
Hold membership on the server, present it on the device. Membership is a record with an activation date, a status and a cancellation date, owned by the API. The application asks the platform what the member's status is rather than deciding locally, and renewal dates are derived server-side from the activation date so the client never computes entitlement.
Replace the card with a code the member can show and share. Each member holds a personal code, rendered in-app as a scannable image and shareable through the standard system share sheet — which is what turns the identity code into the referral mechanism as well.
Model the referral graph on the member record itself. Every member carries both their own code and the code of whoever introduced them. Attribution then becomes a single self-join rather than a separate subsystem — proportionate engineering for a first release, and correct without being elaborate.
Move policy content out of the binary. Terms, privacy policy and scheme rules are managed as content records in the back office and rendered inside the application through a web view. The operator edits them and they are live, with no release cycle involved.
Make configuration a runtime concern. Mail delivery, storage and messaging configuration live in a database-backed settings store with a cached accessor and a back-office editor, so operational changes do not require a code change or a deployment.
Design the image path around the layout, not the other way round. The photo endpoint returns intrinsic dimensions alongside every image URL, so the client's gallery layout can be computed before any image has been downloaded — no reflow, no jumping, no waiting for the first byte to know where things go.
Automate deployment before it is inconvenient not to. Two Capistrano stages driven from CI, with release directories, retained previous releases for rollback, and environment configuration and key material provisioned as shared links rather than baked into a release artefact.
The Solution
Member accounts. Registration and login issuing OAuth2 access tokens, with device and push identifiers captured at registration and refreshed on every sign-in.
Account management. Profile editing with photo upload, a confirmed email-change flow requiring the current address, password change requiring the current password, and tokenised password reset delivered by email and completed through a web form.
Location-aware venue discovery. A venue directory returning distance from the member's position as a sortable field, with free-text search across venue name and description.
Interactive map. Venues rendered as custom map annotations with remotely loaded imagery, over the device's native mapping and location services, with a tap-through to the venue.
Venue profiles. Description, address, contact details, the offers available at that venue, member reviews, and a photo gallery with a full-screen browser.
Member reviews and ratings. Members submit a titled review with a star rating; submissions are upserted so a member holds a single, editable opinion per venue rather than accumulating duplicates.
Membership lifecycle. Activate, retrieve and cancel a membership, with cancellation recording its own date and the renewal date derived server-side from activation.
Membership identity code. A personal member code rendered as a scannable in-app image, shareable through the system share sheet.
Referral programme. Referral codes captured at registration — typed or scanned with the device camera — with a member-facing list of everyone they have introduced.
Editable policy content. Terms, privacy policy and scheme content managed as records in the back office and rendered in-app, updatable without an application release.
Administrative back office. Management of venues, members and administrators, content pages, and a runtime settings area covering mail, storage and messaging configuration, with log inspection available to operators.
Progressive web delivery. A web manifest and service worker supporting installable, offline-capable delivery of the web tier.
Key Features
Distance-ranked venue discovery Great-circle distance from the member's position is computed by the API and returned on every venue, so results arrive already ordered by relevance and one endpoint serves both list and map.
Native map with custom venue annotations Venues plotted on the device's native map with custom annotation views and asynchronously loaded imagery, with direct navigation into the venue profile.
Server-held membership status Membership is a platform record with activation, status and cancellation dates, and a server-derived renewal date — the application reads entitlement rather than deciding it.
In-app membership identity code A personal, scannable member code replacing the physical card, shareable directly from the app.
Referral attribution built into the member record Every member carries both their own code and their introducer's, making the referral graph a single self-join and attribution a query rather than a subsystem.
Camera-based code capture at registration New members scan an introducer's code with the device camera instead of transcribing it, using native camera and metadata capture.
Single-opinion member reviews Titled reviews with star ratings, upserted per member per venue so opinions are editable rather than duplicated.
Layout-aware photo galleries The API returns intrinsic image dimensions with every photo URL so the client's gallery layout resolves before the first image loads, with a full-screen browser for detail.
Content updatable without a release Policy and scheme content managed in the back office and rendered in-app, decoupling legal changes from the app release cycle.
Runtime configuration store Mail, storage and messaging settings held in a cached, database-backed store and edited from the back office, so operational changes need no deployment.
Technical Architecture
Mobile client. A native iOS application built with UIKit over storyboards and XIBs, structured as a side-menu shell wrapping an animated tab bar. Screens are organised into account, discovery, membership and profile modules over a shared base view controller. Device capabilities include native mapping and location services, camera-based code scanning through the system capture stack, and camera and photo-library image selection. Presentation uses a custom waterfall collection layout for galleries, a full-screen photo browser, carousels and slideshows for venue imagery, star-rating components for reviews, and vector animation for membership states.
API layer. A token-authenticated REST API with a dedicated OAuth2 guard, controllers partitioned into consumer, administrative and authentication trees over a shared base controller that provides a uniform success, error and validation-failure envelope so the client has one response shape to handle.
Administrative tier. A server-rendered back office in the same application, session-authenticated and route-group isolated from the API, covering venue, member, administrator, content and settings management, with log inspection for operators.
Data layer. A relational database with member, venue, offer, review, membership, content and settings tables, evolved through versioned migrations. Reference data that is read constantly and written rarely is served through a model-level cache.
Configuration layer. A key/value settings store with a cached accessor, letting mail, storage and messaging configuration be changed at runtime from the back office rather than through code or environment changes.
Storage layer. A filesystem abstraction over image and document handling, so media can be relocated from the application server to object storage without changes to application code.
Delivery pipeline. Capistrano stages for staging and production driven from continuous integration, performing release-directory deployment, database migration, cache clearing, dependency installation and application-server reload, with previous releases retained for rollback and environment configuration and key material provisioned as shared links outside the release artefact.
Flow: Native iOS app → OAuth2 token-authenticated REST API → Laravel application tier → relational database with cached reference reads → server-rendered administrative back office → CI-driven Capistrano deployment to staging and production
Technology Stack
| Category | Technology |
|---|---|
| Backend language | PHP |
| Backend framework | Laravel |
| Database | MySQL with Eloquent; Doctrine DBAL for schema evolution |
| API authentication | OAuth2 access tokens (Laravel Passport) |
| Query caching | Model-level caching layer |
| Admin front-end | Laravel UI, Bootstrap, jQuery, Sass, compiled through Laravel Mix / webpack |
| Admin forms & tables | Laravel Collective HTML, DataTables |
| Web delivery | Progressive Web App manifest and service worker |
| Cross-origin handling | Dedicated CORS middleware |
| HTTP client | Guzzle |
| Operations tooling | In-application log viewer |
| Deployment | Capistrano with a Laravel deployment plugin, two stages |
| Continuous integration | CircleCI pipeline driving deploy, migrate, cache clear and dependency steps |
| Code style | Automated PHP and asset style enforcement |
| Mobile platform | Native iOS — Swift, UIKit, storyboards and XIBs |
| Mobile dependency management | CocoaPods |
| Mobile networking | Alamofire with multipart upload support |
| Mobile imaging | SDWebImage and AlamofireImage for cached remote loading |
| Mobile mapping & location | MapKit and CoreLocation |
| Mobile camera & scanning | AVFoundation metadata capture; camera and photo-library picker |
| Mobile presentation | Custom waterfall collection layout, full-screen photo browser, carousel and slideshow components, animated tab bar, vector animation |
| Mobile ratings UI | Star-rating components |
| Mobile utilities | Keyboard management, progress indicators, network reachability |
Technical Challenges & Solutions
| Challenge | Our Approach |
|---|---|
| Members search by proximity, but the answer has to arrive already ordered | Great-circle distance is computed inside the query from the member's supplied position and returned as a field on every venue, so the API returns a relevance-ordered result and one endpoint serves both the list and the map rather than the client fetching everything and sorting it. |
| Membership status must be authoritative, not assumed by the device | Membership is a server-owned record with activation, status and cancellation dates, and the renewal date is derived server-side from activation. The client asks the platform what the member is entitled to instead of computing it locally, so entitlement logic exists in exactly one place. |
| Attributing referrals without building a separate subsystem | Each member record carries both its own code and the code that introduced it, so the referral graph is a self-join on a single table. Attribution becomes a query rather than a service — proportionate to a first release and correct without over-building. |
| Legal and scheme content changing faster than app releases | Content is managed as records in the back office and rendered inside the application through a web view, decoupling policy changes from the store release cycle entirely. |
| Photo-heavy venue profiles feeling slow on mobile connections | The API returns intrinsic dimensions alongside every image URL so the gallery layout resolves before the first image downloads, combined with cached asynchronous image loading on the client — the layout never reflows and the screen never waits to know its own shape. |
| Operational configuration requiring a code change | Mail, storage and messaging settings live in a database-backed store with a cached accessor and a back-office editor, so operators change configuration at runtime without a deployment. |
| Serving a mobile API and a browser back office from one codebase | Route-group and controller-tree partitioning with separate authentication guards — token-based for the API, session-based for the back office — over shared models, keeping one deployable artefact without blurring the two audiences. |
| Giving the client a single response shape to handle | A shared API base controller emitting a uniform envelope for success, error and validation failure, so the mobile networking layer has one contract to decode rather than per-endpoint special cases. |
| Promoting changes to two environments without manual server work | Capistrano stages driven from CI performing release-directory deployment, migration, cache clearing and application-server reload, with previous releases retained for rollback and environment configuration provisioned as shared links outside the release. |
Security & Reliability
Token-based authentication. The mobile API authenticates through OAuth2 access tokens issued at registration and login, with explicit token revocation on sign-out.
Separated authentication domains. The token-authenticated mobile API and the session-authenticated administrative back office are separate route groups with separate guards, so a credential valid for one surface is not automatically valid for the other.
Verified credential changes. Password changes require the current password, and email changes require confirmation of the current address alongside a matched new address — neither can be performed from a session alone.
Single-use password reset. Password reset issues a token delivered by email and consumed on use, with the reset record removed once the password has been changed.
Server-side validation on every write. Every endpoint that accepts input validates it before persistence and returns validation failures through the same uniform response envelope as every other outcome.
Configuration outside the release artefact. Environment configuration and cryptographic key material are provisioned at deploy time as shared linked files rather than being included in a release, so promoting a build never moves secrets with it.
Rollback-capable deployment. Release-directory deployment retains previous releases, so a problematic change can be reverted without a rebuild.
Recoverable content. Content records use soft deletion, so removals from the back office are reversible rather than destructive.
Operational visibility. Log inspection is available to operators from within the back office, so production behaviour can be examined without server access.
Scalability & Performance
Cached reference reads. Settings and member reference data are read on nearly every request and change rarely, making them the natural candidates for a model-level cache and removing a large share of repeated queries.
Stateless application tier. Token authentication and externalised configuration keep application instances free of local state, so the API tier scales horizontally without session affinity.
Relevance computed once, at the source. Distance ordering happens in the database rather than on the device, so the client transfers and renders a bounded, already-ordered result instead of a full directory.
Client-side image caching. Venue imagery is loaded asynchronously through a caching image layer with in-memory and on-disk retention, which matters in a product where every venue carries a gallery and members browse repeatedly.
Layout resolved before download. Server-supplied image dimensions let the client compute its gallery geometry before any image arrives, eliminating layout passes triggered by image decoding.
Storage abstraction. Media handling goes through a filesystem abstraction, so imagery can be relocated to object storage as volume grows without changes to application code.
Runtime configuration. Because operational settings are read from a cached store rather than compiled in, tuning production behaviour does not require a deployment cycle.
Automated, repeatable releases. CI-driven deployment across two environments keeps promotion consistent and low-effort, which is what makes frequent small releases practical rather than risky.
Business Outcomes
- Members can find participating venues near them, ordered by proximity, at the moment they are deciding where to eat — the question a printed directory could never answer.
- Membership became a live server-held record with activation, status and cancellation dates, rather than a physical object that proves nothing about whether the subscription is current.
- The venue directory is updatable in real time, so members are working from what participates today rather than what participated at print time.
- Recommendations are attributable, because every member record carries the code of whoever introduced them — turning word-of-mouth into something the operator can see and act on.
- Members carry their identity code in the app, and can share it directly, which makes the same artefact serve both entry and referral.
- Member opinion is captured in the platform, with a single editable review per member per venue rather than accumulated duplicates.
- Policy and scheme content changes without an app release, removing a store cycle from what should be an editorial decision.
- Operators administer the platform without developers, covering venues, members, content and runtime configuration from the back office.
- Changes reach staging and production through one automated pipeline, with rollback available — established at the start of the engagement rather than retrofitted.
Why it worked
First releases are their own discipline. The temptation is to build the whole imagined product; the correct move is to build the parts that everything else will stand on, build them properly, and leave the rest deliberately open. A consumer membership product lives or dies on three things — whether the member can find something useful near them, whether the platform knows who they are, and whether the operator can change the content without calling a developer. Those are the things this release was built to get right.
Our team works that way on purpose. We put proximity in the query rather than the client, because relevance is the product's core value and it belongs where the data is. We made membership a server-owned record so entitlement has exactly one home. We modelled referral attribution on the member record itself rather than inventing a subsystem for it, because proportionate engineering at MVP stage is a judgement call that pays for itself later. We moved policy content and operational configuration out of the binary and out of the code, because the operator should not need a release to change a sentence. And we automated deployment to two environments in the first week, because the alternative — manual promotion — is a habit that becomes expensive to break.
Our teams work across Laravel and modern PHP, REST API and OAuth2 design, native iOS in Swift and UIKit, location-aware and camera-driven mobile features, and CI-driven deployment automation — with the product judgement to know which parts of a first release must be built to last, and which are better left simple until the business has learned more.
Final Summary
A discount club had been sold the way discount clubs have always been sold: a card and a printed list of participating venues. Members could not tell which of those venues were near them, could not tell whether a venue still participated, and could not do anything with the card except carry it. The operator, having taken a recurring payment, had almost no visibility of the scheme they were running.
Our team built the first release of a platform that changes the shape of that. A native iOS application over a token-authenticated Laravel API gives members a directory ordered by how close each venue actually is, plotted on a native map, with photo galleries that resolve their layout before the images arrive and reviews that capture a single editable opinion per venue. Membership became a server-held record with its own lifecycle, and the plastic card became an in-app identity code the member can show or share — which is also what makes referrals attributable, since every member record carries the code of whoever introduced them.
Behind it sits the part that determines whether a product like this can be run rather than merely launched: an administrative back office covering venues, members and content; policy text that changes without an app release; operational configuration held in a cached runtime store rather than in code; and a CI-driven deployment pipeline reaching staging and production with rollback, in place from the beginning. It is a foundation built to be extended — which, for a first release, is the only thing worth optimising for.