Work / Mobile app · 2026
Grocery ordering app for a regional grocery chain in Pakistan
A grocery ordering product for a regional chain in Pakistan: an Expo and React Native app for shoppers and a React dashboard for staff, both on an Express and PostgreSQL API. I was the lead developer in a small team, wrote most of the code in the current repository and merged my teammates' pull requests. I hardened the API, added in-app account deletion and wrote the release documents for a Play Store submission; the project is paused at that stage.
- case study
- client work
- mobile app
- client anonymized
- paused
- Client
- Regional grocery chain (Pakistan)
- Type
- Mobile app · E-commerce: grocery ordering app
- Built with
- TypeScript, React Native, Expo, NativeWind, Express, Node.js, Drizzle ORM, PostgreSQL, React, Vite, GitHub Actions, Render, Vercel, Google Maps Platform
- Also uses
- Supabase, Swagger, Jest, Turborepo
- Last active
- 2026
Overview
I was the lead developer of a grocery ordering product for a regional grocery chain in Pakistan: a mobile app for ordering groceries for delivery, and an admin dashboard for the chain's staff.
Customers browse categories and brands, keep a cart, a wishlist and saved addresses, check out with delivery and vouchers, and get order push notifications; staff manage the catalog, home banners and orders.
The work stopped at a build prepared for a Play Store release, with a physical-device beta gate still to pass.
Context and problem
The chain appears to have wanted its own ordering channel: an app for its shoppers, and one dashboard where its staff keep the catalog, banners and orders up to date.
My role
I was the lead developer. In the current repository I wrote most of the code across the API, the customer app and the admin, and merged nearly all of the team's pull requests.
I moved the project off an earlier Turborepo starter onto a simpler single repository, and set up the hosting configuration, the EAS build profiles and a CI workflow.
Teammates wrote the delivery-location picker, the brands and all-products screens, the push-notification foundation and the home-screen redesign. In the earlier repository most of the API was a teammate's; my part there was the initial setup, an order API with Swagger docs, and merging pull requests.
Constraints and requirements
- Store policy: Google Play requires in-app account deletion for apps with sign-up, but order records still had to stay intact.
- Permissions: the app asks only for foreground location, to pick a delivery address, and for notifications; camera, microphone, media, background location, contacts and tracking are not declared.
- Secrets: anything embedded in the app, including the API URL and a platform-restricted maps key, is treated as public, while database, JWT, storage and push credentials stay on the server side.
- Delivery area: addresses are limited to the chain's delivery radius.
Research and planning
Before the release I wrote a documentation set: release readiness, a beta release gate, a QA matrix, a privacy data map, beta notes and the store listing.
The QA matrix separates automated coverage from checks that need a real API, database or physical device, and the beta gate lists the auth, shopping, checkout and post-order scenarios that must pass on physical iOS and Android devices before the build may be called beta-ready.
Solution and user experience
A shopper signs in by email or phone with a one-time code, browses categories, brands, deals and a paginated product list, and builds a cart or a wishlist.
At checkout they choose a saved address inside the delivery area, and the server checks prices, delivery fee, voucher, minimum order and stock before it places the order; afterwards they can track the order and get push updates.
Staff use the admin dashboard to manage products, categories, brands and home banners, and to update or delete orders.
Key features
- Customer app: email and phone sign-in with OTP and email verification, categories, brands, deals, server-side pagination, product detail, cart, wishlist, saved addresses, orders and order tracking.
- Email sign-up: a new account is created only after its email code is verified, and password resets use hashed tokens.
- Delivery location: a picker with radius-restricted autocomplete and reverse geocoding through a server-side maps proxy, built by a teammate.
- Checkout: prices, delivery fee, vouchers, minimum order and stock are validated on the server, and stock is deducted inside the checkout transaction.
- Push: order notifications through Expo push tokens registered with the API.
- Admin: products, categories, brands, home banners and orders.
- Account deletion: in-app deletion anonymizes the user and keeps their orders.
Architecture and stack
Three apps share one repository: the Expo customer app and the React admin both call one Express REST API, which reads and writes PostgreSQL through Drizzle.
- App: Expo 54 and React Native with expo-router and NativeWind, in TypeScript.
- API: Express 5 in TypeScript, with controllers, routes and middleware for an app-secret check and JWT auth.
- Data: PostgreSQL with a Drizzle schema and migrations.
- Admin: React 19 and Vite.
- Builds and hosting: EAS profiles for development, preview APKs and production app bundles; the API is configured for Render and the admin for Vercel.
- CI: a GitHub Actions validate workflow.
Implementation details
I hardened the API for release: production start-up fails closed on missing or placeholder configuration, CORS is restricted, auth endpoints are rate-limited, request bodies are capped at 100 KiB and Swagger is off by default.
The app's API client gives every request a 20-second timeout, never retries a mutation on its own, clears the session on a 401 and keeps it on a 403; checkout is single-flight, so repeated taps on the order button send one request.
One migration removes duplicate wishlist rows, keeping the oldest for each user and product, before it adds a unique constraint. The repository has no down-migrations, so the release doc sets the order instead: back up, migrate once, verify, and restore the backup if verification fails.
Automated tests cover the app's stores, API client and components with Jest, and the customer API and the wishlist migration with Vitest integration tests.
Challenges
- Getting the API to build and run on Render took a run of fixes for the deployment, deprecation flags, the lock file and CORS.
- The store release needed account deletion that keeps order history, a privacy data map, and a beta gate that can only be passed on physical iOS and Android devices.
Trade-offs
Keeping totals and stock on the server means the app is never trusted with prices, which likely costs an extra round-trip and stricter error handling in the app.
Anonymizing a deleted account, rather than deleting its rows, removes the person's details but keeps their orders for the records.
Links
Skills demonstrated
- Mobile app development with Expo and React Native.
- REST API design with Express, Drizzle and PostgreSQL.
- Transactional checkout logic on the server.
- Release engineering: EAS builds, CI, QA planning and release documentation.
- Leading a small team: writing most of the code and merging teammates' pull requests.
- Production hardening and app-store compliance.