02 / 24Next →
E-commerce & store management2026

Royal Bakery

A bakery storefront and store-management system with transactional ordering in Postgres, role-based administration and verified PayHere payments in LKR.

ContextUniversity of Bedfordshire
Year2026
TypeWeb
StatusCompleted
RoleFull-stack engineering
8Tables, each with row-level security on
21API test files
7Design specs, written before implementation

Project media coming soon

Screenshots and demo videos for this case study will appear here.

Overview

Royal Bakery combines the public storefront, customer accounts and staff tools in one Next.js App Router client, backed by a typed Express API and Supabase. Customers browse the catalogue, manage a server-side cart, place orders and pay with PayHere. Admins manage categories, products, images, orders and enquiries.

Next.jsTypeScriptExpressSupabasePayHereTailwind CSSFramer Motion
Challenge

Commerce rules had to hold even when requests raced each other. Two customers buying the last item, a cart changing mid-checkout, or a forged payment callback could each leave orders, stock and payments inconsistent.

Approach

I used three tiers. Next.js handles presentation, an Express and TypeScript REST API owns business rules, and Supabase provides Postgres, Auth and Storage. Order placement runs inside a Postgres function, so it is all-or-nothing. PayHere checkouts are signed on the server and payment notifications are verified before any state changes. I designed each slice in a written spec first and covered it with Vitest tests.

Outcome

The result is a complete commerce system with clearly separated layers, database-enforced consistency and a payment flow built for Sri Lankan transactions.

System architecture
01ClientNext.js App Router · Tailwind · Framer Motion

Separate route groups for the shop, authentication and admin, with server actions where they simplify forms.

02APIExpress · TypeScript · Zod

Routers for catalogue, cart, orders, payments, users and admin. Supabase JWTs are verified in middleware, and requireRole('admin') guards the back office.

03DatabaseSupabase Postgres

Versioned migrations for the schema, a trigger that creates a profile row on signup, row-level security, and SQL functions for placing and cancelling orders.

04Storage & paymentsSupabase Storage · PayHere

Product images go to a storage bucket with sanitised paths. PayHere checkout is MD5-signed on the server, and notifications are verified before order state changes.

Engineering decisions
01

Place orders inside the database

create_order_from_cart runs as one Postgres function. It locks the product rows (FOR UPDATE), checks availability and stock, prices every line from the database rather than from client input, writes the order and its items, decrements stock and clears the cart in a single transaction. Concurrent checkouts can't oversell, and a client can't change a price.

02

Deny direct database access by default

Row-level security is enabled on every table, so the public anon key can't read or write business data on its own. All access goes through the API, which applies authentication and role checks before using a server-only service key.

03

Trust the notification, not the redirect

The browser's return from PayHere never marks an order as paid. Only the server-to-server notification can, after its md5sig is recomputed with the merchant secret and matched. Status codes then map to explicit payment and order states: paid, failed or still pending.

What I built
01Storefront, accounts, cart and checkout in one App Router client
02Transactional order placement and cancellation as Postgres functions with row locks
03Supabase JWT authentication with role-based admin routes
04Signed PayHere checkout and verified payment notifications
05Admin management for catalogue, product images, orders and enquiries
Next projectMagga →