Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vendoo

A full-stack e-commerce marketplace with a product catalog, cart, favorites, and dual buyer/seller roles. The backend is a Node.js/Express REST API on PostgreSQL; the frontend is a React (Vite) single-page application.

Features

  • Catalog of 54 seeded products across 6 categories (Electronics, Clothing, Home & Living, Sports, Books, Beauty), each with price, discount, stock, rating, and review count
  • Category filtering, full-text search (PostgreSQL GIN index, with an ILIKE fallback for partial matches), and 6 sort modes (featured, price low-to-high, price high-to-low, rating, review count, newest)
  • A dedicated product detail page — larger view, quantity picker, stock status, seller attribution, and related products from the same category
  • Cart and favorites, scoped to the signed-in user via their JWT — not a URL parameter
  • Row-level stock locking on checkout-adjacent operations so concurrent requests can't oversell stock
  • JWT authentication with a customer/seller role chosen at registration, enforced by a requireRole middleware
  • Sellers get their own product CRUD (create/update/soft-delete), scoped to products they own
  • Checkout turns a cart into an order inside a single transaction — stock is locked and decremented, a pluggable payment step runs, and the cart is cleared only once payment succeeds
  • Order history for buyers; a per-item order queue with status updates (pending → processing → shipped → delivered) for sellers
  • Centralized request validation (express-validator) and error handling, Helmet + CORS allow-list, and a /health endpoint that checks the database connection

Tech Stack

Backend: Node.js, Express, PostgreSQL (pg), dotenv, JWT (jsonwebtoken), bcryptjs, express-validator, Helmet, CORS, Morgan, Nodemon (dev)

Frontend: React, Vite, lucide-react

Requirements

  • Node.js ≥ 18
  • PostgreSQL ≥ 14

Project Structure

backend/
├── migrations/
│   ├── 001_initial.sql
│   ├── 002_add_orders.sql
│   └── migrate.js
├── seeds/
│   └── seed.js
├── src/
│   ├── app.js
│   ├── config/
│   │   └── db.js
│   ├── controllers/
│   │   ├── authController.js
│   │   ├── cartController.js
│   │   ├── categoryController.js
│   │   ├── favoriteController.js
│   │   ├── orderController.js
│   │   └── productController.js
│   ├── middleware/
│   │   ├── auth.js
│   │   ├── errorHandler.js
│   │   └── validate.js
│   ├── routes/
│   │   ├── auth.js
│   │   ├── cart.js
│   │   ├── categories.js
│   │   ├── favorites.js
│   │   ├── orders.js
│   │   └── products.js
│   └── services/
│       └── paymentService.js
├── .env.example
└── package.json

frontend/
├── index.html
├── vite.config.js
├── src/
│   ├── api.js
│   ├── App.jsx
│   └── main.jsx
└── package.json

(node_modules/ is omitted — it's regenerated by npm install. The frontend stays single-file by design — checkout and order history are components inside App.jsx, and api.js gained an orders module — so no new frontend files were added.)

Getting Started

Database

psql -U postgres
CREATE DATABASE vendoo;
\q

Backend

cd backend
npm install
cp .env.example .env   # fill in DB_HOST, DB_USER, DB_PASSWORD, JWT_SECRET, etc.
npm run migrate         # create tables
npm run seed             # load 54 demo products, categories, and demo users
npm run dev               # http://localhost:3000

Frontend

cd frontend
npm install
npm run dev   # http://localhost:5173

Make sure the backend is reachable at the address configured as BASE_URL in src/api.js (http://localhost:3000/api by default).

Orders are paid through a mock provider by default (PAYMENT_MOCK_MODE=true in .env), so checkout works end-to-end without a real payment account — see the 0.1.1 entry below for how to wire up a live gateway later.

Demo Accounts

Role Email Password
Buyer alici@demo.com Demo1234
Seller satici@demo.com Demo1234

Database Schema

categories              products                    users
──────────              ────────                    ─────
id (PK, serial)         id (PK, serial)              id (PK, uuid)
name                    category_id (FK)              email
slug                    seller_id (FK → users)        name
emoji                   name                           password_hash
sort_order              description                   is_verified
created_at              price                          role ('customer'|'seller')
                        discount                       created_at
                        stock
                        emoji                          cart_items
                        tag                            ──────────
                        rating                          id (PK, serial)
                        review_count                    user_id (FK)
                        is_active                       product_id (FK)
                        created_at / updated_at         quantity
                                                         created_at / updated_at

                                                        favorites
                                                        ─────────
                                                        user_id (FK)   ┐ PK
                                                        product_id(FK) ┘
                                                        created_at

API Reference

Products

Method Endpoint Auth Description
GET /api/products Public List products — query params below
GET /api/products/category/:slug Public Products in one category
GET /api/products/:id Public Single product
POST /api/products Seller Create a product
PUT /api/products/:id Seller Update a product you own
DELETE /api/products/:id Seller Soft-delete a product you own

GET /api/products query parameters:

Parameter Type Default Description
category string Category slug (electronics, clothing, …)
search string Matches name via full-text search + ILIKE
sort string featured featured, price_asc, price_desc, rating, reviews, newest
page number 1 Page number
limit number 20 Items per page (max 100)
sellerId uuid Restrict to one seller's products

Example response:

{
  "data": [
    {
      "id": 5,
      "name": "Mechanical Keyboard RGB",
      "price": "1599.00",
      "discount": 0,
      "final_price": "1599",
      "stock": 60,
      "category_name": "Electronics",
      "category_slug": "electronics"
    }
  ],
  "meta": { "total": 10, "page": 1, "limit": 12, "totalPages": 1, "hasNext": false, "hasPrev": false }
}

Categories

Method Endpoint Auth Description
GET /api/categories Public All categories with product counts

Cart (all require auth — the cart is the caller's own, via the JWT)

Method Endpoint Description
GET /api/cart Get the current user's cart
POST /api/cart Add an item — body: productId, quantity
PUT /api/cart/item/:itemId Update an item's quantity
DELETE /api/cart/item/:itemId Remove one item
DELETE /api/cart Clear the cart

Favorites (all require auth)

Method Endpoint Description
GET /api/favorites List favorites
GET /api/favorites/check/:productId Check favorite status
POST /api/favorites Add a favorite — body: productId
DELETE /api/favorites/:productId Remove a favorite

Auth

Method Endpoint Description
POST /api/auth/register body: email, name, password, role? (customer|seller)
POST /api/auth/login body: email, password
GET /api/auth/me Auth required

Orders (all require auth)

Method Endpoint Description
POST /api/orders Checkout — body: shippingAddress. Converts the caller's cart into an order
GET /api/orders The caller's own order history
GET /api/orders/:id A single order the caller owns, with its items
GET /api/orders/seller/items Seller-only — order items across all orders that belong to the caller
PUT /api/orders/items/:itemId/status Seller-only, ownership-checked — body: status (pending|processing|shipped|delivered|cancelled)

Frontend Integration

import { products, cart, auth, orders } from './api';

// Products
const { data, meta } = await products.list({ category: 'electronics', sort: 'price_asc' });

// Cart (token comes from the signed-in session)
await cart.add(productId, 1, token);

// Auth
const { token, user } = await auth.login(email, password);

// Orders
const order = await orders.create(shippingAddress, token);
const myOrders = await orders.list(token);

Version History

0.1.2 (Current) — Product Detail Page

  • Frontend-only release — the existing GET /api/products/:id endpoint already returned everything a detail view needed (description, seller name via a join, rating, stock), so no backend or schema changes were required
  • New ProductDetailView, added inside the existing App.jsx alongside the other components: large product view, quantity stepper, live stock status, "Sold by" attribution for seller-listed products, and a "You might also like" row (same-category products, computed client-side from the already-loaded catalog — no extra request)
  • ProductCard is now clickable (title/image area) to open the detail view, with keyboard support (Enter/Space) and role="button"; the favorite and "Add to Cart" buttons stop event propagation so quick actions from the grid still work without navigating away
  • addToCart now accepts an optional quantity (defaults to 1), so the detail page's quantity stepper can add more than one at a time while the card's quick-add button keeps its original one-tap behavior
  • Category bar hides while viewing a product; the search box and the Vendoo wordmark both return to the catalog automatically

0.1.1 — Order & Payment Flow

  • Database: new orders and order_items tables (002_add_orders.sql). order_items.product_id/order_id are INT, matching the existing SERIAL primary keys on products/orders; seller_id is UUID, matching users(id) — denormalized from the product at purchase time so a seller's order queue stays correct even if the product changes later
  • Checkout: POST /api/orders reads the caller's cart, row-locks each product (FOR UPDATE, the same pattern used to fix the addToCart race condition in 0.0.2), checks stock, decrements it, snapshots the price paid, runs the order through paymentService, and clears the cart — all inside one transaction, so a failed payment rolls back the stock deduction too
  • Payments: new services/paymentService.js, a small provider-agnostic interface. It ships in mock mode (PAYMENT_MOCK_MODE=true) so checkout works end-to-end with no real payment account; going live means implementing the same charge() function against a real provider's SDK (Stripe, iyzico, ...)
  • Order management: buyers get GET /api/orders (history) and GET /api/orders/:id (detail); sellers get GET /api/orders/seller/items and PUT /api/orders/items/:itemId/status to move their own items through pending → processing → shipped → delivered
  • Frontend: checkout modal, an order-history drawer for buyers, and an "Orders" tab with a live status dropdown for sellers — all added as new components inside the existing App.jsx, plus an orders module in api.js. Seller dashboard gained a fourth "Pending Orders" stat card

0.1.0 — Full Backend Integration for Buyer/Seller Roles

  • Database: role (customer/seller) added to users, seller_id added to products — both folded into the initial migration
  • Auth: register, login, and me carry role end-to-end, including inside the JWT payload; register validates role is one of customer/seller
  • Authorization: requireRole(...roles) middleware; seller-only routes protected with [authenticate, requireRole("seller")]
  • Seller product management: POST /api/products, PUT /api/products/:id, DELETE /api/products/:id, each checking the caller owns the product; deletes are soft (is_active = false)
  • Frontend: a dedicated AuthScreen (role selection + login/register) and separate CustomerApp / SellerApp views chosen by user.role; the session persists in sessionStorage
  • Localization: all source comments, API error/log messages, category and seed-product data, and UI copy translated to English (category slugs changed accordingly, e.g. elektronikelectronics); full-text search configuration switched from 'turkish' to 'english' to match; both package.json version fields now read 0.1.0
  • Rebranded from "Pazaryeri" to Vendoo across the codebase (package names, database name default, page title, and the in-app wordmark); version stays at 0.1.0

0.0.3 — Dual-Role (Buyer/Seller) Experience (Frontend Demo)

  • Role-selection screen (Buyer / Seller) with animated cards, a demo-account autofill button, and a combined login/register form
  • Buyer experience: header shows name and avatar; cart and favorites persist across logout/login; seller-added products appear in the catalog tagged "Seller Product"
  • Seller dashboard: sidebar with Panel (stat cards + recent products), My Products (table with edit/delete and a delete-confirmation modal), and New Product (form with an emoji picker and a live discount preview)
  • Note: at this stage the app was a self-contained frontend demo — auth, cart, favorites, and seller products all ran on window.storage, with no backend calls yet

0.0.2 — Security Hardening, Performance & Auth Foundation

  • Fixed a race condition in addToCart by wrapping it in a transaction with SELECT ... FOR UPDATE row locking, so concurrent requests can no longer oversell stock; the stock check also accounts for the quantity already in the cart
  • userId is no longer read from the URL; a JWT-based authenticate middleware was added and applied to every cart and favorites route
  • Wired up express-validator across all routes, with centralized 422 error responses via a shared validate middleware
  • Product search now matches via a GIN full-text index (to_tsvector/plainto_tsquery), with an ILIKE fallback for partial matches
  • Added a 10 kb JSON body size limit; /health now runs SELECT 1 to verify the database connection instead of only confirming the process is alive
  • Split the single combined routes file into auth.js, cart.js, favorites.js, and categories.js
  • Added a _migrations tracking table so re-running migrations skips what was already applied; added password_hash and is_verified columns to users
  • New: authController.js with bcrypt password hashing, JWT issuing, and register / login / getMe endpoints, with password-policy validation (minimum 8 characters, an uppercase letter, and a digit)
  • Frontend: memoized components and derived values, cleaned up a setTimeout memory-leak risk, added a search debounce, made product cards stock-aware, and added accessibility attributes

0.0.1 — Initial Release (MVP)

  • Frontend: catalog of 54 products across 6 categories with discounts, tags, and ratings; real-time search; category filter bar; sort options (price, rating, review count); add to cart directly from the product card; toggle favorites; cart and favorites drawers; discount badges
  • Backend: Node.js + Express REST API on PostgreSQL; schema for categories, products, users, cart_items, and favorites with foreign keys, indexes, and updated_at triggers; endpoints for products (filter/search/sort/pagination), categories, cart, and favorites; centralized error handling, CORS + Helmet, and connection pooling

Roadmap

  • Real payment gateway integration (Stripe or iyzico) in place of the mock provider, plus webhook handling for asynchronous payment confirmation
  • Order status transition rules (e.g. prevent skipping from pending straight to delivered)
  • Admin dashboard for product and stock management
  • Redis caching for frequent queries
  • Product image uploads (AWS S3 or Cloudinary)

About

Full-stack e-commerce marketplace with buyer/seller roles — Node.js/Express/PostgreSQL REST API, React (Vite) frontend.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages