Week 4

Full-Stack REST API & Web Dashboard

4~7 hours

1. Project Overview & Architecture

Welcome to Major Project 5: Full-Stack REST API & Web Dashboard! In this final major project, you will bridge back-end Python logic with modern web architecture. You will construct a complete, lightweight RESTful backend server using Flask that handles resource management over HTTP (GET, POST, DELETE), enforces payload validation, manages status codes, and serves an interactive real-time web dashboard.

Project Core Objectives

  • RESTful Architecture: Build routes corresponding to standard HTTP verbs (GET for retrieval, POST for creation, DELETE for removal).
  • JSON Payload Validation: Inspect incoming HTTP headers and body data, returning explicit HTTP error codes (400 Bad Request, 404 Not Found) when payloads fail schema checks.
  • State Synchronization: Maintain an in-memory data store with unique primary key generation and dynamic state updating.
  • Frontend Integration: Serve an embedded Single-Page Application (SPA) dashboard that uses asynchronous JavaScript (fetch) to update the UI without reloading the page.

2. Functional Requirements & Feature Specifications

Your REST API & Web Dashboard must fulfill the following technical endpoints and specifications:

Endpoint / Route HTTP Verb Functional Purpose Success / Error Codes
/ GET Serves the HTML/JS Web Dashboard UI. 200 OK
/api/items GET Returns array of all recorded inventory items as JSON. 200 OK
/api/items POST Accepts JSON payload and appends a new resource record. 201 Created, 400 Bad Request
/api/items/<id> DELETE Removes a specific resource item by unique integer ID. 200 OK, 404 Not Found

3. Complete Reference Implementation

Study the self-contained Flask REST API server and embedded dashboard application below:

from flask import Flask, jsonify, request, render_template_string

app = Flask(__name__)

# In-memory database datastore
inventory = [
    {"id": 1, "name": "Developer Laptop", "category": "Electronics", "qty": 12, "price": 1299.99},
    {"id": 2, "name": "Ergonomic Desk", "category": "Furniture", "qty": 8, "price": 349.50}
]
next_id = 3

# Single-Page Web Dashboard UI (HTML/JS)
HTML_DASHBOARD = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>REST API Dashboard</title>
    <style>
        body { font-family: system-ui, -apple-system, sans-serif; margin: 2rem; background: #f8f9fa; color: #212529; }
        h1 { font-size: 1.5rem; margin-bottom: 1rem; }
        .card { background: white; padding: 1.5rem; border-radius: 8px; border: 1px solid #dee2e6; margin-bottom: 1.5rem; }
        .form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.5rem; margin-bottom: 1rem; }
        input, button { padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; font-size: 0.9rem; }
        button { background: #0d6efd; color: white; border: none; cursor: pointer; font-weight: 600; }
        button:hover { background: #0b5ed7; }
        button.delete-btn { background: #dc3545; padding: 0.25rem 0.5rem; font-size: 0.8rem; }
        button.delete-btn:hover { background: #bb2d3b; }
        table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
        th, td { text-align: left; padding: 0.75rem; border-bottom: 1px solid #dee2e6; font-size: 0.9rem; }
        th { background: #e9ecef; }
    </style>
</head>
<body>
    <h1>📦 Inventory REST API & Dashboard</h1>
    
    <div class="card">
        <h3 style="margin-top:0;">Add New Item (POST /api/items)</h3>
        <div class="form-grid">
            <input type="text" id="itemName" placeholder="Item Name" />
            <input type="text" id="itemCategory" placeholder="Category" />
            <input type="number" id="itemQty" placeholder="Quantity" />
            <input type="number" step="0.01" id="itemPrice" placeholder="Price ($)" />
        </div>
        <button onclick="addItem()">Submit item via API</button>
    </div>

    <div class="card">
        <h3 style="margin-top:0;">Live Inventory State (GET /api/items)</h3>
        <table>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>Category</th>
                    <th>Quantity</th>
                    <th>Price</th>
                    <th>Action</th>
                </tr>
            </thead>
            <tbody id="inventoryTable"></tbody>
        </table>
    </div>

    <script>
        async function loadInventory() {
            const res = await fetch('/api/items');
            const data = await res.json();
            const tbody = document.getElementById('inventoryTable');
            tbody.innerHTML = '';
            
            data.forEach(item => {
                tbody.innerHTML += `
                    <tr>
                        <td>#${item.id}</td>
                        <td><strong>${item.name}</strong></td>
                        <td>${item.category}</td>
                        <td>${item.qty}</td>
                        <td>$${item.price.toFixed(2)}</td>
                        <td><button class="delete-btn" onclick="deleteItem(${item.id})">Delete</button></td>
                    </tr>
                `;
            });
        }

        async function addItem() {
            const name = document.getElementById('itemName').value.trim();
            const category = document.getElementById('itemCategory').value.trim();
            const qty = parseInt(document.getElementById('itemQty').value);
            const price = parseFloat(document.getElementById('itemPrice').value);

            if (!name || !category || isNaN(qty) || isNaN(price)) {
                alert('Please fill out all input fields accurately.');
                return;
            }

            const response = await fetch('/api/items', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ name, category, qty, price })
            });

            if (response.ok) {
                document.getElementById('itemName').value = '';
                document.getElementById('itemCategory').value = '';
                document.getElementById('itemQty').value = '';
                document.getElementById('itemPrice').value = '';
                loadInventory();
            } else {
                const err = await response.json();
                alert('API Error: ' + err.error);
            }
        }

        async function deleteItem(id) {
            const response = await fetch(`/api/items/${id}`, { method: 'DELETE' });
            if (response.ok) {
                loadInventory();
            } else {
                alert('Failed to delete item.');
            }
        }

        // Initial fetch on view mount
        loadInventory();
    </script>
</body>
</html>
"""

@app.route("/")
def index():
    """Serves the Single-Page Web Dashboard."""
    return render_template_string(HTML_DASHBOARD)

@app.route("/api/items", methods=["GET"])
def get_all_items():
    """Returns the current list of inventory records as JSON."""
    return jsonify(inventory), 200

@app.route("/api/items", methods=["POST"])
def create_new_item():
    """Validates payload and inserts a new inventory record."""
    global next_id
    payload = request.get_json()

    # Payload validation checks
    if not payload:
        return jsonify({"error": "Missing or invalid JSON payload"}), 400

    required_fields = ["name", "category", "qty", "price"]
    for field in required_fields:
        if field not in payload:
            return jsonify({"error": f"Missing required field: '{field}'"}), 400

    try:
        new_record = {
            "id": next_id,
            "name": str(payload["name"]).strip(),
            "category": str(payload["category"]).strip(),
            "qty": int(payload["qty"]),
            "price": float(payload["price"])
        }
    except (ValueError, TypeError):
        return jsonify({"error": "Data type mismatch for qty or price"}), 400

    inventory.append(new_record)
    next_id += 1
    return jsonify(new_record), 201

@app.route("/api/items/<int:item_id>", methods=["DELETE"])
def remove_item(item_id):
    """Deletes an item record by ID if it exists."""
    global inventory
    target_item = next((item for item in inventory if item["id"] == item_id), None)

    if not target_item:
        return jsonify({"error": f"Item with ID #{item_id} not found"}), 404

    inventory = [item for item in inventory if item["id"] != item_id]
    return jsonify({"message": f"Successfully deleted item #{item_id}"}), 200

if __name__ == "__main__":
    print("🚀 REST API Server running at http://127.0.0.1:5000")
    app.run(debug=True, port=5000)

4. Common Project Pitfalls

⚠️ Project Watchlist:

  • Incorrect HTTP Status Codes: Always return specific status codes (e.g., 201 Created on POST success, 400 Bad Request for invalid input, and 404 Not Found for missing resources) instead of generic 200 codes for everything.
  • Unvalidated Request Payloads: Accessing key names like payload["name"] without verifying their presence will crash the backend server with an unhandled KeyError.
  • Cross-Origin Resource Sharing (CORS): If your web dashboard is hosted on a different domain or port than your API server, browsers will block network calls unless CORS headers are configured.

5. Interactive Project Workspace

Project Workspace

Build & Test: REST API & Web Dashboard

Use the editor below to craft your REST API endpoints. Try adding a PUT endpoint to support updating existing inventory quantities!

0% complete
Project Feature Checklist

6. Key Takeaways & Graduation Achievement

  • REST APIs provide structured web interfaces for interacting with application resources over HTTP.
  • Using proper HTTP status codes makes back-end services predictable and easy to integrate with front-end applications.
  • Input validation protects servers from malformed data payloads and runtime exceptions.

🎓 Python Course Completed!

Congratulations on finishing all lessons and major projects! You have built a solid, real-world practical understanding of modern Python development.