Abona

Abona Shop πŸ›οΈ

A full-stack e-commerce web application built for the Thai market. Rose Γ— deep plum brand identity, JWT-based authentication, Stripe payments, and a complete admin panel.

Author: Hermann N’zi Ngenda

Live Site: https://abona-3ve.pages.dev


Screenshots

Homepage

Homepage

Login

Login

Checkout & Payment

Payment

Admin Dashboard

Dashboard

Admin Products

Admin Products


Project Structure

Abona/
β”œβ”€β”€ frontend/                   # All client-side code
β”‚   β”œβ”€β”€ index.html              # Main shop page
β”‚   β”œβ”€β”€ login.html
β”‚   β”œβ”€β”€ register.html
β”‚   β”œβ”€β”€ forgot-password.html
β”‚   β”œβ”€β”€ reset-password.html
β”‚   β”œβ”€β”€ checkout.html
β”‚   β”œβ”€β”€ orders.html
β”‚   β”œβ”€β”€ tracking.html
β”‚   β”œβ”€β”€ product.html
β”‚   β”œβ”€β”€ wishlist.html
β”‚   β”œβ”€β”€ settings.html
β”‚   β”œβ”€β”€ images/                 # Product images, logos, icons
β”‚   β”œβ”€β”€ styles/
β”‚   β”‚   β”œβ”€β”€ shared/             # general.css, amazon-header.css
β”‚   β”‚   └── pages/              # Per-page stylesheets
β”‚   β”œβ”€β”€ scripts/
β”‚   β”‚   β”œβ”€β”€ amazon.js           # Homepage product grid + filters
β”‚   β”‚   β”œβ”€β”€ auth.js             # Auth state, header rendering
β”‚   β”‚   β”œβ”€β”€ checkout.js         # Checkout page entry
β”‚   β”‚   β”œβ”€β”€ checkout/
β”‚   β”‚   β”‚   β”œβ”€β”€ orderSummary.js
β”‚   β”‚   β”‚   └── paymentSummary.js
β”‚   β”‚   └── utils/
β”‚   β”‚       β”œβ”€β”€ api.js          # API_BASE constant
β”‚   β”‚       β”œβ”€β”€ money.js        # formatCurrency helper
β”‚   β”‚       └── darkmode.js
β”‚   └── data/
β”‚       β”œβ”€β”€ cart.js             # Cart state + API calls
β”‚       └── deliveryOptions.js
β”‚
β”œβ”€β”€ backend/                    # Node.js / Express API
β”‚   β”œβ”€β”€ server.js               # App entry point
β”‚   β”œβ”€β”€ .env                    # Environment variables (not committed)
β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”œβ”€β”€ auth.js
β”‚   β”‚   β”œβ”€β”€ products.js
β”‚   β”‚   β”œβ”€β”€ cart.js
β”‚   β”‚   β”œβ”€β”€ orders.js
β”‚   β”‚   β”œβ”€β”€ payment.js
β”‚   β”‚   β”œβ”€β”€ reviews.js
β”‚   β”‚   β”œβ”€β”€ wishlist.js
β”‚   β”‚   β”œβ”€β”€ coupons.js
β”‚   β”‚   β”œβ”€β”€ users.js
β”‚   β”‚   β”œβ”€β”€ uploads.js
β”‚   β”‚   └── admin.js
β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   └── auth.js             # JWT verification middleware
β”‚   β”œβ”€β”€ db/
β”‚   β”‚   β”œβ”€β”€ connection.js       # MySQL pool
β”‚   β”‚   β”œβ”€β”€ schema.sql          # Full database schema + triggers
β”‚   β”‚   └── seed.js             # Sample data seeder
β”‚   β”œβ”€β”€ utils/
β”‚   β”‚   └── email.js            # Brevo HTTP API email templates
β”‚   └── admin/                  # Admin panel HTML + assets
β”‚
β”œβ”€β”€ tests/                      # Jasmine unit tests
└── README.md

Quick Start (Local Development)

Prerequisites

Tool Version
Node.js 18+
MySQL / MariaDB 8.0+ (XAMPP works)
VS Code + Live Server Any

1 β€” Database

mysql -u root -p < backend/db/schema.sql

2 β€” Backend

cd backend
cp .env.example .env
npm install
npm start                   # http://localhost:3000

3 β€” Frontend

Open frontend/index.html with VS Code Live Server (configured via .vscode/settings.json to serve from frontend/).

Site runs at http://127.0.0.1:5500. Make sure CLIENT_ORIGIN=http://127.0.0.1:5500 in .env.


Environment Variables

PORT=3000

# MySQL
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=abona_shop

# JWT
JWT_SECRET=long_random_string
JWT_ADMIN_SECRET=different_long_random_string

# Arcjet
ARCJET_KEY=ajkey_...
ARCJET_ENV=development

# CORS
CLIENT_ORIGIN=http://127.0.0.1:5500

# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...

# Brevo (email)
BREVO_API_KEY=xkeysib_...
BREVO_SENDER_EMAIL=you@gmail.com
ADMIN_EMAIL=admin@yourdomain.com

Deployment

Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     HTTPS      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Cloudflare Pages   β”‚ ─────────────► β”‚   Render Backend    β”‚
β”‚  (Frontend)         β”‚                β”‚   (Express API)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                  β”‚ SSL
                                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                       β”‚    Aiven MySQL      β”‚
                                       β”‚    (Database)       β”‚
                                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 1 β€” Aiven MySQL (Database)

  1. Sign up at aiven.io β†’ Create Service β†’ MySQL β†’ Free plan
  2. Once running, click your service β†’ Overview tab β†’ copy:
    • Host, Port, User, Password, Database name
  3. Download the CA Certificate (required for SSL)
  4. Upload your schema:
    mysql --ssl-ca=ca.pem -h your-host -P port -u user -p dbname < backend/db/schema.sql
    
  5. Update your Render environment variables with these DB credentials (see Step 2)

Step 2 β€” Render (Backend)

  1. Push your repo to GitHub
  2. Go to render.com β†’ New Web Service
  3. Connect your GitHub repo
  4. Settings:
    • Root directory: backend
    • Build command: npm install
    • Start command: node server.js
    • Environment: Node
  5. Add all environment variables in the Render dashboard:
PORT=3000
DB_HOST=your-aiven-host
DB_PORT=your-aiven-port
DB_USER=your-aiven-user
DB_PASSWORD=your-aiven-password
DB_NAME=defaultdb
DB_SSL=true
JWT_SECRET=...
JWT_ADMIN_SECRET=...
ARCJET_KEY=...
ARCJET_ENV=production
CLIENT_ORIGIN=https://your-project.pages.dev
STRIPE_SECRET_KEY=sk_live_...
STRIPE_PUBLISHABLE_KEY=pk_live_...
BREVO_API_KEY=...
BREVO_SENDER_EMAIL=...
ADMIN_EMAIL=...
  1. Deploy β€” your backend will be live at https://abona-backend.onrender.com

Step 3 β€” Cloudflare Pages (Frontend)

  1. Go to pages.cloudflare.com β†’ Create a project
  2. Connect your GitHub repo
  3. Settings:
    • Root directory: frontend
    • Build command: (leave empty β€” static site)
    • Output directory: . (dot)
  4. Before deploying, update frontend/scripts/utils/api.js:
    export const API_BASE = 'https://abona-backend.onrender.com';
    
  5. Deploy β€” your frontend will be at https://abona.pages.dev
  6. Go back to Render β†’ update CLIENT_ORIGIN=https://abona.pages.dev

Step 4 β€” SSL for Aiven (backend connection)

Add SSL support to backend/db/connection.js:

ssl: process.env.DB_SSL === 'true' ? { rejectUnauthorized: true } : false

Custom Domain (optional)

In Cloudflare Pages β†’ Custom Domains β†’ add abona-shop.com. DNS is managed automatically since you’re already on Cloudflare.


How It Works

Authentication Flow

Browser                          Backend                        Database
  β”‚                                 β”‚                               β”‚
  β”œβ”€β”€β”€ POST /api/auth/register ────►│                               β”‚
  β”‚    { name, email, password }    │── bcrypt.hash(password) ─────►│
  β”‚                                 │── INSERT INTO users ──────────►│
  β”‚                                 │◄───────────────────────────────
  │◄── Set-Cookie: token (JWT) ──────                               β”‚
  β”‚                                 β”‚                               β”‚
  β”œβ”€β”€β”€ GET /api/auth/me ───────────►│── jwt.verify(cookie) ────────►│
  │◄── { user } ─────────────────────                               β”‚

Cart & Orders

Add to Cart ──► POST /api/cart
Checkout    ──► POST /api/payment/create-intent  (Stripe PaymentIntent)
            ──► stripe.confirmPayment()
            ──► POST /api/orders  (saves order, clears cart)

Email Notifications

Event                        Customer          Admin
─────────────────────────────────────────────────────
Order placed                 βœ… Confirmation   βœ… Alert
Order paid (admin)           βœ… Notification   β€”
Order shipped (admin)        βœ… Notification   β€”
Order delivered (admin)      βœ… Notification   β€”
Order cancelled by customer  β€”                βœ… Alert
Order cancelled by admin     βœ… Notification   β€”
Low stock after order        β€”                βœ… Alert
Password reset               βœ… Reset link     β€”

Dark Mode

<script>
  if (localStorage.getItem('darkMode') === 'true')
    document.documentElement.classList.add('dark');
</script>

Runs before CSS loads β€” prevents flash of unstyled content.


Database

Schema Overview

users ──────────────────────────────────────────────────────────┐
  β”‚                                                              β”‚
  β”œβ”€β”€ sessions          (multi-device login tracking)           β”‚
  β”œβ”€β”€ addresses         (saved shipping addresses)              β”‚
  β”œβ”€β”€ cart_items ──────── products ──── product_images          β”‚
  β”œβ”€β”€ wishlists                    └─── product_variants        β”‚
  β”‚     └── wishlist_items         └─── product_categories      β”‚
  β”œβ”€β”€ reviews                           └── categories          β”‚
  └── orders ─────────────────────────────────────────────────► β”‚
        β”œβ”€β”€ order_items                                          β”‚
        β”œβ”€β”€ order_status_logs                                    β”‚
        └── payments                                            β”‚
                                                                β”‚
coupons ── coupon_uses β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
password_resets

Key Tables

Table Purpose
users Account data, role (user/admin)
products Catalog with denormalised star rating
product_variants Size/color SKUs with individual stock
cart_items Per-user persistent cart
orders Order header with frozen shipping snapshot
order_items Line items with product data snapshot
payments Stripe PaymentIntent records
coupons Discount codes (percentage or fixed)
reviews One review per user per product
password_resets Secure tokens for password reset (1hr expiry)

Important Queries

Fetch cart:

SELECT c.*, p.name, p.image, p.price_cents
FROM cart_items c
JOIN products p ON c.product_id = p.id
WHERE c.user_id = ?

Place an order (transaction):

BEGIN;
  INSERT INTO orders (...) VALUES (...);
  INSERT INTO order_items (...) VALUES (...);
  UPDATE products SET stock = stock - ? WHERE id = ?;
  DELETE FROM cart_items WHERE user_id = ?;
COMMIT;

Rating trigger (auto on review change):

UPDATE products
SET stars        = (SELECT ROUND(AVG(stars), 2) FROM reviews
                    WHERE product_id = NEW.product_id AND is_approved = TRUE),
    rating_count = (SELECT COUNT(*) FROM reviews
                    WHERE product_id = NEW.product_id AND is_approved = TRUE)
WHERE id = NEW.product_id;

Validate coupon:

SELECT * FROM coupons
WHERE code = ?
  AND is_active = TRUE
  AND (expires_at IS NULL OR expires_at >= NOW())
  AND (max_uses IS NULL OR uses_count < max_uses)

Arcjet β€” Security Layer

Incoming Request
      β”‚
      β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚         Arcjet Shield        β”‚  ← blocks bots, scrapers, attack patterns
  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
  β”‚      Sliding Window         β”‚  ← rate limiting per IP
  β”‚   (10 req/hr on register,   β”‚
  β”‚    10 req/15min on login,   β”‚
  β”‚    3 req/15min on reset)    β”‚
  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
  β”‚     Email Validation        β”‚  ← rejects disposable / invalid emails
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚
      β–Ό
  Route Handler

Stripe β€” Payment Flow

Frontend                    Backend                      Stripe
   β”‚                           β”‚                            β”‚
   β”œβ”€β”€ GET /api/config ────────►│                            β”‚
   │◄── { publishableKey } ──────                            β”‚
   β”‚                           β”‚                            β”‚
   β”œβ”€β”€ POST /api/payment/      β”‚                            β”‚
   β”‚   create-intent ──────────►│── paymentIntents.create() β–Ίβ”‚
   β”‚                           │◄── { client_secret } ────────
   │◄── { clientSecret } ────────                            β”‚
   β”‚                           β”‚                            β”‚
   β”‚  stripe.confirmPayment()  β”‚                            β”‚
   β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚
   │◄── status: succeeded ──────┼─────────────────────────────
   β”‚                           β”‚                            β”‚
   β”œβ”€β”€ POST /api/orders ───────►│── saves order to DB         β”‚
   │◄── { orderId } ─────────────                            β”‚

API Endpoints

Method Endpoint Auth Description
POST /api/auth/register βœ— Create account
POST /api/auth/login βœ— Sign in, set cookie
POST /api/auth/logout βœ— Clear cookie
GET /api/auth/me Cookie Get current user
POST /api/auth/forgot-password βœ— Send reset email
POST /api/auth/reset-password βœ— Set new password
GET /api/products βœ— List all products
GET /api/products/:id βœ— Single product
GET /api/cart βœ“ Get user cart
POST /api/cart βœ“ Add item
PATCH /api/cart/:id βœ“ Update qty / delivery
DELETE /api/cart/:id βœ“ Remove item
GET /api/orders βœ“ Order history
POST /api/orders βœ“ Place order
GET /api/orders/:id βœ“ Single order
PATCH /api/orders/:id/cancel βœ“ Cancel order
POST /api/payment/create-intent βœ“ Stripe PaymentIntent
GET /api/wishlist βœ“ Get wishlist
POST /api/wishlist/:id βœ“ Add to wishlist
DELETE /api/wishlist/:id βœ“ Remove from wishlist
POST /api/coupons/validate βœ“ Validate coupon
GET /api/reviews/:productId βœ— Product reviews
POST /api/reviews/:productId βœ“ Submit review

Admin Panel

Local: http://localhost:3000/admin/login Production: https://abona-backend.onrender.com/admin/login

Protected by JWT_ADMIN_SECRET. Features:


Tech Stack

Layer Technology
Frontend Vanilla HTML, CSS, ES Modules
Backend Node.js, Express.js
Database MySQL / MariaDB
Auth JWT (httpOnly cookies) + bcryptjs
Payments Stripe (Payment Element)
Security Arcjet (shield + rate limit + email validation)
Email Brevo HTTP API
Hosting β€” Frontend Cloudflare Pages
Hosting β€” Backend Render
Hosting β€” Database Aiven MySQL

Built by Hermann N’zi Ngenda