A multi-vendor e-commerce platform where independent sellers list and manage their own products under a single storefront, customers browse and buy across all vendors in one cart, and administrators oversee the marketplace from a dedicated back office.
The application is split across two repositories:
| Repository | What it is |
|---|---|
| Easy-Shop (this one) | Vue 3 single-page application covering the storefront, vendor panel and admin panel |
| Easyshop | Spring Boot 2.6 REST API backed by MySQL, with Stripe Checkout for payments |
The two are developed together and share an API contract, so they must be deployed as a pair — see Authentication Model. Paths shown below as backend/Backend/... refer to files in the API repository.
- Overview
- Features
- Architecture
- Tech Stack
- Getting Started
- Configuration
- Authentication Model
- Security Notes
- Project Structure
- API Reference
- Available Scripts
- Known Limitations
- License
Easy Shop models a marketplace with three user types, each with its own registration, sign-in and interface:
| Role | Entry point | Purpose |
|---|---|---|
| Customer | /signup, /signin |
Browse categories, search products, keep a wishlist and cart, pay via Stripe, track orders |
| Vendor | /vsells, /vlogsin |
Seller dashboard — publish products, upload imagery, review orders placed against their own listings |
| Admin | /adminlogin |
Back office — manage the category taxonomy, review users and vendors, confirm and update orders |
The storefront is built on Bootstrap 5; the vendor and admin panels use CoreUI Vue, giving each back office a sidebar, breadcrumb and stat-widget layout.
Customer and vendor accounts must confirm their email address before they can sign in — registration sends a verification link, and signIn refuses accounts that are not yet enabled.
Storefront (customer)
- Category browsing and per-category product listings
- Product search with a dedicated results page
- Product detail pages with image galleries
- Wishlist
- Persistent cart with quantity updates and item removal
- Stripe Checkout with success and failure return pages
- Order placement, order history and order detail views
- Profile editing and password change
Vendor panel
- Dashboard with sales widgets
- Product create / edit / list / delete, scoped to the vendor's own catalogue
- Product image upload and management
- Ordered-products view covering only that vendor's sales
- Profile editing and password change
Admin panel
- Dashboard with marketplace widgets
- Category CRUD plus category image management
- Customer and vendor directories
- Order review, confirmation and status updates
- Profile editing and password change
Browser (Vue 3 SPA, :8080)
│ JSON over HTTP; session token in the Authorization header
▼
Spring Boot REST API (:8084) ──► MySQL (easyshop)
│ ──► SMTP (account verification mail)
└──────────────────────► Stripe (Checkout sessions, payment verification)
The frontend never talks to MySQL or Stripe's secret API directly. Prices, order totals and account identity are all resolved server-side; see Authentication Model.
Frontend
| Concern | Choice |
|---|---|
| Framework | Vue 3 (Options API) |
| Build | Vue CLI 5, Babel |
| Routing | Vue Router 4 (history mode, with role-aware navigation guards) |
| State | Vuex 4 |
| UI | Bootstrap 5 (storefront), CoreUI Vue 4 (dashboards) |
| HTTP | Axios, via a shared client that attaches the bearer token |
| Payments | Stripe.js (Checkout redirect) |
Backend
| Concern | Choice |
|---|---|
| Framework | Spring Boot 2.6.3 (Spring MVC) |
| Persistence | Spring Data JPA / Hibernate, MySQL 8 |
| Password hashing | BCrypt (spring-security-crypto) |
| Payments | stripe-java |
| Spring Boot Mail (SMTP) | |
| Build | Maven (wrapper included) |
- JDK 11+ and MySQL 8 (for the API)
- Node.js 16+ and npm (for the web client)
- A Stripe account — you need the test publishable and secret keys
- An SMTP account for verification email (Gmail requires an App Password)
CREATE DATABASE easyshop;
CREATE USER 'easyshop_app'@'localhost' IDENTIFIED BY 'a-strong-password';
GRANT ALL PRIVILEGES ON easyshop.* TO 'easyshop_app'@'localhost';Use a dedicated account rather than root. Schema objects are generated by Hibernate on first run.
Clone the API repository alongside this one:
git clone https://github.com/Alphax978/Easyshop.git
cd Easyshop/Backend
cp .env.example .env # then fill in every blankLoad the variables and start the API:
# bash
set -a && source .env && set +a
./mvnw spring-boot:run
# PowerShell
Get-Content .env | ForEach-Object { if ($_ -match '^([^#=]+)=(.*)$') { [Environment]::SetEnvironmentVariable($Matches[1].Trim(), $Matches[2].Trim()) } }
.\mvnw.cmd spring-boot:runThe API listens on http://localhost:8084. It will not start without DB_USERNAME, DB_PASSWORD, STRIPE_SECRET_KEY and the mail credentials — that is deliberate, so a deployment cannot silently fall back to placeholder secrets.
Administrator registration is not open to the public. Generate a bootstrap secret, set it as ADMIN_BOOTSTRAP_TOKEN, restart the API, then:
curl -X POST http://localhost:8084/backend/Admin/signup \
-H "Content-Type: application/json" \
-H "X-Admin-Bootstrap-Token: <your ADMIN_BOOTSTRAP_TOKEN>" \
-d '{"firstName":"Ada","lastName":"Lovelace","email":"admin@example.com","password":"choose-a-long-one"}'The bootstrap token only works while no administrator exists. After the first account, further admins must be created by an existing admin sending their own Authorization: Bearer <token>. Clear ADMIN_BOOTSTRAP_TOKEN from the environment once you are done.
cd frontend
cp .env.example .env.local # set VUE_APP_API_URL and your Stripe publishable key
npm install
npm run serveThe dev server runs on http://localhost:8080 with hot reload. npm run build emits a production bundle to frontend/dist/; serve it from any static host that rewrites unknown paths to index.html so history-mode routing works.
Nothing secret lives in the repository. Both halves read configuration from the environment, and both ship a committed .env.example documenting every key.
Backend — backend/Backend/.env.example
| Variable | Purpose |
|---|---|
DB_URL, DB_USERNAME, DB_PASSWORD |
MySQL connection |
SERVER_PORT |
API port (default 8084) |
APP_BASE_URL |
Public URL of the frontend; Stripe return URLs are built from it |
APP_ALLOWED_ORIGINS |
Comma-separated CORS allowlist |
ADMIN_BOOTSTRAP_TOKEN |
One-time secret permitting creation of the first admin |
STRIPE_SECRET_KEY |
Stripe secret key — backend only, never shipped to the browser |
MAIL_HOST, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD |
SMTP for verification mail |
JPA_SHOW_SQL |
SQL logging; leave false outside local debugging |
Frontend — frontend/.env.example
| Variable | Purpose |
|---|---|
VUE_APP_API_URL |
Base URL of the backend API |
VUE_APP_STRIPE_PUBLISHABLE_KEY |
Stripe publishable key (pk_…), which is designed to be public |
Anything prefixed
VUE_APP_is compiled into the JavaScript bundle and is readable by anyone who loads the page. Only the publishable Stripe key belongs there; the secret key (sk_…) goes in the backend environment alone.
Sign-in returns an opaque session token, which the browser stores and sends on every subsequent request:
Authorization: Bearer <token>
Three rules hold throughout the API:
- Identity is derived from the token, never from the request. Endpoints that act on "your" account — profile update, password change, order history, vendor sales — take no account id at all. The server looks up the token's owner. Supplying somebody else's id is not a way in, because there is nowhere to supply one.
- Authentication and authorisation are separate checks. Holding a valid token proves who you are; each endpoint separately confirms the record you are reaching for is yours. Orders verify the buyer, products verify the seller.
- Money is calculated server-side. Checkout accepts only a product id and a quantity. Unit prices are read from the database and the Stripe session is confirmed as
paidbefore an order is recorded, so neither the price nor the fact of payment can be asserted by the client.
Passwords are hashed with BCrypt at cost factor 12. Accounts predating this change still hold an MD5 digest; those are accepted at sign-in and transparently re-hashed to BCrypt on the next successful login, so no one is locked out and the old digests drain away on their own.
The frontend also carries role-aware router guards, but treat them as a usability feature — they keep panels from rendering for signed-out visitors. Guards in a browser can always be bypassed, so the server authorises every request independently.
This codebase went through a security review; the fixes are described above. Points worth carrying forward:
- Rotate any credential that was ever committed or shared. Configuration now comes from the environment, but a secret that previously sat in a file should be considered burned. Rotate the database password, Stripe keys and SMTP app password.
.gitignorecoverstarget/,.env, and the upload directories. Maven copiesapplication.propertiesintotarget/classes, so an unignored build directory will leak whatever the file holds.- Uploads are restricted to
jpg/jpeg/png/gif/webp, stored under a generated UUID filename, and resolved through a containment check so a crafted name cannot escape the storage directory. Upload endpoints require an admin (catalogue imagery) or vendor (product imagery) session. - CORS is an allowlist, driven by
APP_ALLOWED_ORIGINS. Avoid reintroducing@CrossOriginon individual controllers — a per-controller annotation overrides the central policy. - Error responses omit stack traces and exception messages (
server.error.include-stacktrace=never), so internals are not disclosed to clients.
Sensible next steps, not yet implemented:
- Move order fulfilment to a Stripe webhook. The API verifies payment status before recording an order and rejects a reused session id, but a webhook is the durable path — it survives a customer closing the tab before the redirect completes.
- Give session tokens an expiry and rotation policy. They are currently long-lived and issued once per account.
- Add rate limiting on sign-in and registration.
- Consider httpOnly cookies instead of
localStorage, which would put tokens out of reach of injected scripts.
Easyshop/ # API repository
└── Backend/
│ ├── .env.example # documented configuration keys
│ ├── pom.xml
│ └── src/main/
│ ├── java/com/EasyShop/ecommerce/
│ │ ├── controller/ # REST endpoints
│ │ ├── service/ # business logic, Stripe, mail, storage
│ │ ├── security/ # AuthGuard (identity), PasswordService (BCrypt)
│ │ ├── repository/ # Spring Data repositories
│ │ ├── model/ # JPA entities
│ │ ├── dto/ # request/response shapes
│ │ ├── exceptions/ # typed errors + HTTP status mapping
│ │ └── config/ # CORS, storage locations
│ └── resources/
│ └── application.properties # reads entirely from the environment
Easy-Shop/ # web client repository (this one)
└── frontend/
├── .env.example
├── public/index.html
└── src/
├── api/
│ ├── http.js # shared Axios client; attaches bearer token
│ └── session.js # "who am I" against the /me endpoints
├── router/index.js # routes + role-aware guards
├── components/ # shared storefront components
├── views/ # storefront pages
├── Admin/ # admin shell (CoreUI)
└── aVendor/ # vendor shell (CoreUI)
All paths are relative to the API base URL. Auth shows what the endpoint requires.
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/backend/user/signup |
— | Register a customer; sends verification mail |
POST |
/backend/user/signIn |
— | Sign in, returns a token |
GET |
/backend/user/verify-email?token= |
— | Confirm an address |
GET |
/backend/user/me |
Customer | The caller's own record |
POST |
/backend/user/update |
Customer | Update own profile |
POST |
/backend/user/updatepass |
Customer | Change own password (current password required) |
GET |
/backend/user/reflect |
Admin | Customer directory |
POST |
/backend/Vendor/signup · /signIn |
— | Vendor registration and sign-in |
GET |
/backend/Vendor/me |
Vendor | The caller's own record |
POST |
/backend/Vendor/update · /updatepass |
Vendor | Update own profile / password |
GET |
/backend/Vendor/all · /vreflect |
Admin | Vendor directory |
POST |
/backend/Admin/signup |
Admin or bootstrap token | Create an administrator |
POST |
/backend/Admin/signIn |
— | Administrator sign-in |
GET |
/backend/Admin/me |
Admin | The caller's own record |
POST |
/backend/Admin/update · /updatepass |
Admin | Update own profile / password |
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/backend/category/show |
— | List categories (drives storefront nav) |
POST |
/backend/category/create |
Admin | Create a category |
POST |
/backend/category/update/{id} |
Admin | Update a category |
DELETE |
/backend/category/delete/{name} |
Admin | Delete a category |
GET |
/backend/product/showun |
— | List all products |
GET |
/backend/product/{id} |
— | Product detail |
GET |
/backend/product/products/search/{query} |
— | Search |
GET |
/backend/product/vendorshow |
Vendor | The caller's own listings |
POST |
/backend/product/add |
Vendor | Create a listing |
POST |
/backend/product/update/{id} |
Vendor (owner) | Update own listing |
DELETE |
/backend/product/delete/{id} |
Vendor (owner) | Delete own listing |
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/backend/cart/ |
Customer | Own cart |
POST |
/backend/cart/add |
Customer | Add an item |
PUT |
/backend/cart/update |
Customer | Change quantity |
DELETE |
/backend/cart/delete/{itemId} |
Customer | Remove an item |
GET |
/backend/wishlist/ · /gets |
Customer | Own wishlist |
POST |
/backend/wishlist/add |
Customer | Add to wishlist |
DELETE |
/backend/wishlist/delete/{id} |
Customer | Remove from wishlist |
POST |
/backend/order/create-checkout-session |
Customer | Stripe session; prices resolved server-side |
POST |
/backend/order/add?sessionId= |
Customer | Record an order once Stripe reports it paid |
GET |
/backend/order/ |
Customer | Own order history |
GET |
/backend/order/{id} |
Customer (owner) | Own order detail |
GET |
/backend/order/all |
Admin | Every order |
GET |
/backend/order/allbyseller |
Vendor | Own sales |
POST |
/backend/order/statusupdate/{date} · /orderupdate/{date} |
Admin | Update order status |
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/backend/fileUpload/ |
Admin | Upload catalogue imagery |
GET |
/backend/fileUpload/ · /files/{name} |
— | List / fetch catalogue imagery |
POST |
/backend/vendor/fileUpload/ |
Vendor | Upload product imagery |
GET |
/backend/vendor/fileUpload/ · /files/{name} |
— | List / fetch product imagery |
Frontend (from frontend/)
| Command | Description |
|---|---|
npm run serve |
Development server with hot reload |
npm run build |
Production bundle in dist/ |
npm run lint |
Lint and auto-fix |
Backend (from backend/Backend/)
| Command | Description |
|---|---|
./mvnw spring-boot:run |
Run the API |
./mvnw clean package |
Build an executable JAR |
./mvnw test |
Run tests |
- No automated test suite. Neither half has meaningful test coverage; the security changes described above were verified by review and a frontend production build, not by tests.
- Order fulfilment is redirect-driven. Payment is verified against Stripe and session ids cannot be replayed, but a webhook would be more robust — see Security Notes.
- Session tokens do not expire. One long-lived token is issued per account.
ddl-autoisupdate. Convenient in development; production deployments should manage schema with migrations instead.- The bundled
index.htmlloads jQuery 3.2.1 and Bootstrap 4 from CDNs, both of which have published advisories, whilepackage.jsonbundles Bootstrap 5. Worth reconciling. - The frontend still ships a
rolevalue inlocalStorageto choose which/meendpoint to call. It is a routing hint only — the server never trusts it.
No license file is present. All rights are reserved by the author until one is added — open an issue if you would like to use this project.