All work
◆ Personal · Live

JJ Grocery

Personal project2025
Built the database, api and interface layers

The problem

My dad runs a retail grocery business. Orders came in by phone and lived on paper. He needed customers to be able to order staples themselves, and he needed to run the whole shop (stock, orders, customers, pricing) without learning anything complicated.

Constraints

  • Delivery is limited to one pincode, because that is the shop’s real delivery radius
  • The admin user is a shopkeeper, not an operator, so the panel had to be obvious
  • No payment gateway onboarding, no monthly fees, no paperwork
  • Order history must stay accurate even when prices change

Decisions

Decision 01

Order placement is a single atomic transaction

Validate address, validate cart, check stock per line item, snapshot price and discount onto the order items, decrement available quantity, increment sold quantity, clear the ordered items from the cart, commit. Any failure rolls the entire thing back. Snapshotting the price means a later price change can never rewrite order history.

Trade-off

A longer transaction holding more locks, in exchange for never having a partially-placed order.

Decision 02

A UPI QR code instead of a payment gateway

The confirmation page generates a upi:// deep link with the amount and order number encoded, so it opens PhonePe, GPay or Paytm pre-filled. My dad marks it paid from the admin panel. No gateway fees, no onboarding, working from day one. Razorpay is wired into config for when volume justifies it.

Trade-off

Payment confirmation is manual rather than automatic: the right call at this volume, and reversible later.

Decision 03

A generic, fully-typed repository layer

getRepository<M extends Model>(modelName) returns typed CRUD, pagination and soft-delete for any Sequelize model. All nine repositories became thin type-safe wrappers instead of nine copies of the same query code.

Decision 04

WhatsApp alerts that cannot break checkout

New orders fire a Twilio WhatsApp message to my dad’s phone after the commit: fire-and-forget, config-gated, failures logged rather than thrown. A Twilio outage can never fail a customer’s order.

Outcome

  • Customers order over phone-OTP login and pay by COD or UPI
  • Admin dashboard covers daily orders and revenue, low-stock alerts, order status, and customer lifetime spend
  • Delivery-fee logic runs server-side: free above ₹500, ₹15 below

What I'd change

I audited my own codebase and wrote up what was broken before fixing it: a review bug silently dropping identifiers, a cart-merge that ran on a plain object so save() no-opped, wide-open CORS, and unthrottled OTP endpoints. Writing that list first made the fixes straightforward.