DEV Community

smith
smith

Posted on

Build a Full E-Commerce App with Flask & React (Complete Guide) ver 2

🛒 Build a Full E-Commerce App with Flask & React (Complete Tutorial)

In this tutorial, you’ll build a full working e-commerce system with:

  • Flask REST API backend (__init__.py)
  • React + Vite frontend
  • SQLite database
  • User authentication
  • Editable user accounts
  • Order system
  • Cart (localStorage)
  • VAT support (UK 20%)
  • JSON API responses in browser

📁 Project Structure

ecommerce/
├── backend/
│   └── __init__.py
├── shop.db
└── frontend/
    ├── index.html
    └── src/
        ├── api.js
        ├── main.jsx
        ├── App.jsx
        └── pages/
            ├── Home.jsx
            ├── Shop.jsx
            ├── Cart.jsx
            ├── Account.jsx
            ├── Orders.jsx
            └── Admin.jsx
Enter fullscreen mode Exit fullscreen mode


`


⚙️ Backend Setup (Flask)

Install dependencies

bash
pip install flask flask-cors bcrypt


backend/init.py

`python
from flask import Flask, request, jsonify
from flask_cors import CORS
import sqlite3
import bcrypt
from datetime import datetime

VAT = 0.20

def create_app():
app = Flask(name)
CORS(app)

def db():
    conn = sqlite3.connect("shop.db")
    conn.row_factory = sqlite3.Row
    return conn

def init_db():
    conn = db()

    conn.executescript("""
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT,
        email TEXT UNIQUE,
        password TEXT,
        first_name TEXT,
        last_name TEXT,
        role TEXT DEFAULT 'user',
        account_created TEXT,
        last_login TEXT
    );

    CREATE TABLE IF NOT EXISTS products (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT,
        description TEXT,
        price REAL,
        stock INTEGER
    );

    CREATE TABLE IF NOT EXISTS orders (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER,
        username TEXT,
        total REAL,
        created_at TEXT
    );

    CREATE TABLE IF NOT EXISTS order_items (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        order_id INTEGER,
        product_name TEXT,
        product_desc TEXT,
        qty INTEGER,
        price REAL,
        vat_price REAL
    );
    """)

    conn.commit()
    conn.close()

init_db()

@app.route("/")
def home():
    return jsonify({"status": "API running"})

# ───────── AUTH ─────────
@app.route("/register", methods=["POST"])
def register():
    d = request.json
    hashed = bcrypt.hashpw(d["password"].encode(), bcrypt.gensalt())

    conn = db()
    conn.execute("""
        INSERT INTO users(username, email, password, account_created)
        VALUES (?, ?, ?, ?)
    """, (d["username"], d["email"], hashed.decode(), datetime.now().isoformat()))
    conn.commit()
    conn.close()

    return jsonify({"msg": "registered"})

@app.route("/login", methods=["POST"])
def login():
    d = request.json
    conn = db()

    user = conn.execute(
        "SELECT * FROM users WHERE email=?",
        (d["email"],)
    ).fetchone()

    if not user:
        return jsonify({"error": "not found"}), 404

    if not bcrypt.checkpw(d["password"].encode(), user["password"].encode()):
        return jsonify({"error": "wrong password"}), 401

    conn.execute(
        "UPDATE users SET last_login=? WHERE id=?",
        (datetime.now().isoformat(), user["id"])
    )

    conn.commit()
    conn.close()

    return jsonify({
        "user_id": user["id"],
        "username": user["username"],
        "role": user["role"]
    })

# ───────── PRODUCTS ─────────
@app.route("/products")
def products():
    conn = db()
    rows = conn.execute("SELECT * FROM products").fetchall()
    conn.close()
    return jsonify([dict(r) for r in rows])

# ───────── VAT FUNCTION ─────────
def vat(price):
    return round(price * (1 + VAT), 2)

# ───────── CHECKOUT ─────────
@app.route("/checkout", methods=["POST"])
def checkout():
    d = request.json
    conn = db()

    user = conn.execute(
        "SELECT username FROM users WHERE id=?",
        (d["user_id"],)
    ).fetchone()

    total = 0

    for item in d["cart"]:
        p = conn.execute(
            "SELECT * FROM products WHERE id=?",
            (item["id"],)
        ).fetchone()

        total += p["price"] * item["qty"]

    cur = conn.cursor()

    cur.execute("""
        INSERT INTO orders(user_id, username, total, created_at)
        VALUES (?, ?, ?, ?)
    """, (d["user_id"], user["username"], total, datetime.now().isoformat()))

    order_id = cur.lastrowid

    for item in d["cart"]:
        p = conn.execute(
            "SELECT * FROM products WHERE id=?",
            (item["id"],)
        ).fetchone()

        conn.execute("""
            INSERT INTO order_items(
                order_id, product_name,
                product_desc, qty,
                price, vat_price
            )
            VALUES (?, ?, ?, ?, ?, ?)
        """, (
            order_id,
            p["name"],
            p["description"],
            item["qty"],
            p["price"],
            vat(p["price"])
        ))

    conn.commit()
    conn.close()

    return jsonify({"msg": "order created"})

# ───────── USER ACCOUNT ─────────
@app.route("/me")
def me():
    user_id = request.args.get("user_id")

    conn = db()
    user = conn.execute(
        "SELECT * FROM users WHERE id=?",
        (user_id,)
    ).fetchone()

    conn.close()
    return jsonify(dict(user))

@app.route("/me", methods=["PUT"])
def update_me():
    d = request.json

    conn = db()
    conn.execute("""
        UPDATE users
        SET username=?, first_name=?, last_name=?
        WHERE id=?
    """, (d["username"], d["first_name"], d["last_name"], d["user_id"]))

    conn.commit()
    conn.close()

    return jsonify({"msg": "updated"})

# ───────── ORDERS ─────────
@app.route("/me/orders")
def my_orders():
    user_id = request.args.get("user_id")

    conn = db()

    orders = conn.execute(
        "SELECT * FROM orders WHERE user_id=?",
        (user_id,)
    ).fetchall()

    result = []

    for o in orders:
        items = conn.execute(
            "SELECT * FROM order_items WHERE order_id=?",
            (o["id"],)
        ).fetchall()

        result.append({
            "order": dict(o),
            "items": [dict(i) for i in items]
        })

    conn.close()
    return jsonify(result)

return app
Enter fullscreen mode Exit fullscreen mode

app = create_app()

if name == "main":
app.run(debug=True)
`


🌐 Frontend Setup (React + Vite)


src/api.js

js
export const API = "http://127.0.0.1:5000";


src/main.jsx

`jsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";

ReactDOM.createRoot(document.getElementById("root")).render();
`


src/App.jsx

`jsx
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";

import Home from "./pages/Home";
import Shop from "./pages/Shop";
import Cart from "./pages/Cart";
import Account from "./pages/Account";
import Orders from "./pages/Orders";
import Admin from "./pages/Admin";

export default function App() {
const user = JSON.parse(localStorage.getItem("user") || "{}");

return (


Home |
Shop |
Cart |
Account |
Orders |
{user.role === "admin" && Admin}

  <Routes>
    <Route path="/" element={<Home />} />
    <Route path="/shop" element={<Shop />} />
    <Route path="/cart" element={<Cart />} />
    <Route path="/account" element={<Account />} />
    <Route path="/orders" element={<Orders />} />
    <Route path="/admin" element={<Admin />} />
  </Routes>
</BrowserRouter>
Enter fullscreen mode Exit fullscreen mode

);
}
`


📄 Pages

Home.jsx

jsx
export default function Home() {
return <h1>Welcome to the Store 🛒</h1>;
}


Shop.jsx

`jsx
import { useEffect, useState } from "react";
import { API } from "../api";

export default function Shop() {
const [products, setProducts] = useState([]);

useEffect(() => {
fetch(API + "/products")
.then(r => r.json())
.then(setProducts);
}, []);

const add = (p) => {
const cart = JSON.parse(localStorage.getItem("cart") || "[]");
const found = cart.find(i => i.id === p.id);

if (found) found.qty++;
else cart.push({ ...p, qty: 1 });

localStorage.setItem("cart", JSON.stringify(cart));
Enter fullscreen mode Exit fullscreen mode

};

return (


{products.map(p => (

{p.name}


add(p)}>Add to Cart

))}

);
}
`

Cart.jsx

`jsx
import { API } from "../api";

export default function Cart() {
const cart = JSON.parse(localStorage.getItem("cart") || "[]");
const user = JSON.parse(localStorage.getItem("user") || "{}");

const checkout = async () => {
await fetch(API + "/checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ user_id: user.user_id, cart })
});

localStorage.removeItem("cart");
alert("Order placed");
Enter fullscreen mode Exit fullscreen mode

};

return (


{cart.map(i => (

{i.name} x{i.qty}


))}
Checkout

);
}
`

Account.jsx (EDIT USER)

`jsx
import { useEffect, useState } from "react";
import { API } from "../api";

export default function Account() {
const user = JSON.parse(localStorage.getItem("user") || "{}");
const [form, setForm] = useState({});

useEffect(() => {
fetch(API + "/me?user_id=" + user.user_id)
.then(r => r.json())
.then(setForm);
}, []);

const save = async () => {
await fetch(API + "/me", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...form, user_id: user.user_id })
});

alert("Saved");
Enter fullscreen mode Exit fullscreen mode

};

return (


setForm({ ...form, username: e.target.value })} />
setForm({ ...form, first_name: e.target.value })} />
setForm({ ...form, last_name: e.target.value })} />
Save

);
}
`

Orders.jsx

`jsx
import { useEffect, useState } from "react";
import { API } from "../api";

export default function Orders() {
const user = JSON.parse(localStorage.getItem("user") || "{}");
const [orders, setOrders] = useState([]);

useEffect(() => {
fetch(API + "/me/orders?user_id=" + user.user_id)
.then(r => r.json())
.then(setOrders);
}, []);

return (


{orders.map(o => (

Order #{o.order.id}


{o.items.map(i => (

{i.product_name} x{i.qty}


))}

))}

);
}
`

Admin.jsx

jsx
export default function Admin() {
return <h1>Admin Panel</h1>;
}


✅ DONE

You now have a complete working full-stack e-commerce system:

✔ Flask API backend
✔ React frontend
✔ Account editing
✔ Order history
✔ Cart system
✔ VAT pricing
✔ JSON API in browser
✔ Clean file structure


Top comments (0)