Mini Inventory & Order System

A Laravel API and React single-page app for products, stock and orders — built so that overselling and duplicate orders are impossible by design, not by luck.

  • Laravel 13
  • PHP 8.4
  • React 19
  • TypeScript
  • MySQL 8
  • Redis
  • Docker
  • AWS S3
  • AWS EC2
Backend
Laravel 13 · PHP 8.4
Frontend
React 19 · TypeScript · Vite
Tests
81 feature + 5 concurrency
Local setup
One Docker command

Overview

A small inventory and order system, built as a technical assessment. Staff browse products and place orders; admins manage products, adjust stock, and see and cancel every order.

The scenario that drives the whole design: two customers try to buy the last unit of a product at the same instant. Exactly one order must succeed, and a customer who double-clicks or retries after a timeout must never end up with two orders.

Two roles

Admin and staff, enforced with Laravel policies. Staff see and cancel only their own orders.

No overselling

Stock is checked and decremented against a locked row, never against a value read earlier.

Safe retries

Every order carries an idempotency key, so a replayed request returns the original order.

Tech Stack

Backend

Laravel 13 on PHP 8.4, MySQL 8, Redis through predis for cache, queue and sessions, Laravel Sanctum token authentication.

Frontend

React 19 with TypeScript, Vite and React Router. A typed fetch client with one API module per resource; auth and cart as context providers.

Tooling

Docker Compose for the whole stack, PHPUnit for the two test suites, oxlint for the front end, GitHub Actions for CI/CD.

Architecture

Business logic lives in two services rather than in controllers or models. Form Requests handle validation and authorisation, and API Resources shape the responses. Placing an order runs in a single database transaction:

  1. Claim the idempotency key. The order row is inserted first; a unique index on user and key makes that an atomic claim, so a concurrent retry is handed the original order.
  2. Lock each product row with SELECT … FOR UPDATE, in ascending product-id order, so two multi-item orders can never deadlock each other.
  3. Check and decrement against the locked value. A second buyer waits for the first transaction to commit, then reads the already-decremented stock.
  4. Roll everything back if any line is short — including the order row — and return 409 Conflict with the available and requested quantities.
  • Pessimistic locking was chosen over version columns because this is many requests contending for one hot row with a very short critical section: a row lock queues them for milliseconds instead of making most of them fail and retry.
  • Admin stock adjustments are relative (+25, −4) and use the same lock; absolute writes are rejected because they would silently overwrite concurrent order decrements.
  • Money is never a float: DECIMAL columns, decimal strings in JSON, and bcmath on the server.
  • Order lines snapshot the product name, SKU and price, so later price changes or deletions never rewrite order history.

The main suite (81 tests) runs on in-memory SQLite. Because SQLite ignores row locks, a second suite of 5 concurrency tests runs against real MySQL: it spawns separate PHP processes, each with its own connection, and releases them at the same moment — two buyers for one unit, ten buyers for three units, five retries with the same key, and five concurrent cancels.

Deployment

Locally, the whole system boots with one command. There is no .env to copy and no composer install, npm install, key generation, migration or seeding to run by hand:

docker compose up --build -d

That starts MySQL, Redis, the Laravel API and the Vite front end. Entrypoint scripts wait for a real database connection, migrate, and seed demo data only if the database is empty, so restarting is safe. The login page then has one-click buttons for the admin and staff demo accounts, and the seed data includes a product with exactly one unit in stock for trying the race by hand.

On AWS, the front end and back end are deployed separately by a GitHub Actions pipeline on every push to main:

  • Frontend → S3. The React app is type-checked and built, and the static output is synced to an S3 bucket for static hosting.
  • Backend → EC2. After the test job passes, the pipeline connects to the EC2 instance over SSH, pulls the latest code, rebuilds the API, MySQL and Redis containers with Docker Compose, and runs the migrations.
  • The same Docker Compose definition runs on a laptop and on the server, so the environment that was tested is the one that is deployed.

Have a system to build?

Tell me what it needs to do. I’m open to full stack, Laravel, backend and Node.js roles.

Start a conversation

Dhaka, Bangladesh · Available for remote opportunities