🛒 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
`
⚙️ 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
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>
);
}
`
📄 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));
};
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");
};
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");
};
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)