Time to Review the README

Open the README of the Flask project and work through it. This is how you will get the backend app running on your localhost. Note, that some familiarity with tools like Python, GitHub, and code editors (e.g., VSCode) is assumed.

Files and Directories in this Project

These are some of the key files and directories in this project. Understanding these files will speed up your development.

  • README.md: This file contains instructions for setting up the necessary tools and cloning the project. A README file is a standard component of all properly set up GitHub projects.

  • main.py: This Python source file is used to run the project. Running this file starts a Flask web server locally on localhost. During development, this is the file you use to run, test, and debug the project.

  • __init__.py: This file defines the Flask app, loads environment variables (from .env), and sets up security (like CORS) and database connections (SQLite or RDS). It acts as the main configuration and initialization hub for your backend.

  • Dockerfile and docker-compose.yml: These files are used to run and test the project in a Docker container. They allow you to simulate the project’s deployment on a server, such as an AWS EC2 instance. Running these files helps ensure that your tools and dependencies work correctly on a clean machines.

  • instances: This directory is the standard location for storing data files that you want to remain on the server. For example, SQLite database files can be stored in this directory. Files stored in this location will persist after web application restart, everyting outside of instances will be recreated at restart.

  • api: This directory contains code that receives and responds to requests from external servers. It serves as the interface between the external world and the logic and code in the rest of the project.

  • model: This directory holds class definitions for interacting with the database (like SQLAlchemy models). Models can also connect to third-party APIs or machine learning libraries. By keeping data logic in model, you make your code reusable and keep the api code simple and clean.

  • requirements.txt: This file lists the dependencies required to turn this Python project into a Flask/Python project. It may also include other backend dependencies, such as dependencies for working with a database.

  • .gitignore: This file specifies elements to be excluded from version control. Files are excluded when they are derived and not considered part of the project’s original source. In the VSCode Explorer, you may notice some files appearing dimmed, indicating that they are intentionally excluded from version control based on the rules defined in .gitignore.

Flask Server

As a developer you will create or use a Flask app/server. This lesson will diverge into some interactive steps right now to help you understand the basic code elements in a Flask server with RESTful endpoints.

Note: This minimal, single-file server is just for learning. In real projects, code is split up for:

  • Routes (API endpoints)
  • Models (data and database)
  • Config (settings)
  • Logic (functions, helpers)

Later, you’ll see how these parts are organized across multiple files.

Installing dependencies

In a production server, the requirements.txt file lists all the packages you need to install, for example:

flask
flask_cors
flask_restful

Normally, you would run:

pip install -r requirements.txt

But since we’re working in the pages repository and focusing on Flask usage, we’ll install the needed packages manually below.

# The following lines are required packages for Flask execution
!pip install flask
!pip install flask_cors
!pip install flask_restful

Start a Python/Flask Server

This starts on Port 5001 and supports two endpoints / and /api/data.

  • The /api/data endpoint now uses a model class to store and manage data, following best practices for larger systems.
  • The API supports both GET (read all data) and POST (create new data) requests, matching CRUD naming conventions.
  • This structure makes it easier to expand with more endpoints and features as your project grows.
%%python --bg 

# Refactored: Use CRUD naming (read, create) in InfoModel
from flask import Flask, jsonify, request
from flask_cors import CORS
from flask_restful import Api, Resource

# --- Flask app and CORS setup ---
app = Flask(__name__)
CORS(app, supports_credentials=True, origins='*')
#--- Flask-RESTful API setup ---
api = Api(app)

# --- Model class for InfoDb with CRUD naming ---
class InfoModel:
    # Initialize Model with sample data
    def __init__(self):
        self.data = [
            {
                "FirstName": "John",
                "LastName": "Mortensen",
                "DOB": "October 21",
                "Residence": "San Diego",
                "Email": "jmortensen@powayusd.com",
                "Owns_Cars": ["2015-Fusion", "2011-Ranger", "2003-Excursion", "1997-F350", "1969-Cadillac", "2015-Kuboto-3301"]
            },
            {
                "FirstName": "Shane",
                "LastName": "Lopez",
                "DOB": "February 27",
                "Residence": "San Diego",
                "Email": "slopez@powayusd.com",
                "Owns_Cars": ["2021-Insight"]
            }
        ]

    # Read method to get all entries
    def read(self):
        return self.data

    # Create method to add a new entry
    def create(self, entry):
        self.data.append(entry)
        entry['id'] = len(self.data) - 1
        return entry

# Instantiate the model
info_model = InfoModel()

# --- API Resource Definition ---
class DataAPI(Resource):
    
    # GET method to retrieve all entries
    def get(self):
        return jsonify(info_model.read())
    
    # POST method to add a new entry, requires validation
    def post(self):
        try:
            # extract JSON data from raw body of the request
            entry = request.get_json()
            # sample validation of user input, in production you should do something more robust
            if not entry:
                return {'message': "No data provided"}, 400
            if 'FirstName' not in entry or len(entry['FirstName'].strip()) <= 1:
                return {'message': 'FirstName is missing, or is less than 2 characters'}, 400
            if 'LastName' not in entry or len(entry['LastName'].strip()) == 0:
                return {'message': 'LastName is required'}, 400
            # Add entry and return it with ID
            return {'message': "Entry added successfully", "body": info_model.create(entry)}, 201
        except Exception as e:
            # Catch any unexpected server error
            return {'message': f"Server error: {str(e)}"}, 500

# Add resource to API to establish endpoint    
api.add_resource(DataAPI, '/api/data')

# Wee can use @app.route for HTML endpoints, this will be style for Admin UI
@app.route('/')
def say_hello():
    html_content = """
    <html>
    <head>
        <title>Hello</title>
    </head>
    <body>
        <h2>Hello, World!</h2>
    </body>
    </html>
    """
    return html_content

if __name__ == '__main__':
    app.run(port=5001)

Test the Python/Flask server using Linux

This script discovers the running flask process on your machine using Linux commands.

  1. lsof - list open files
  2. lsof and awk return the process id, so ps can list details, the vericle bar is called a pipe. A pipe flows output from one command to the next.
  3. curl is a Linux utiltity that is easiest way to test if web server is responding
%%script bash

# After app.run(), see the the Python open files on port 5001
echo "Python open files on port 5001" 
lsof -i :5001
# see the the Python process 
echo
echo "Python process"
lsof -i :5001 | awk '/Python/ {print $2}' | xargs ps
# show ontent of the Python server using curl
echo
echo "Content of the Python root endpoint (aka /), using curl",  
curl http://localhost:5001/
Python open files on port 5001
COMMAND   PID          USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  67706 johnmortensen    3u  IPv4 0x28b12545108683cd      0t0  TCP localhost:commplex-link (LISTEN)

Python process
COMMAND   PID          USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  67706 johnmortensen    3u  IPv4 0x28b12545108683cd      0t0  TCP localhost:commplex-link (LISTEN)

Python process
  PID   TT  STAT      TIME COMMAND
67706   ??  S      0:00.10 /opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/Resources/Python.app/Contents/MacOS/Python

Content of the Python root endpoint (aka /), using curl,
  PID   TT  STAT      TIME COMMAND
67706   ??  S      0:00.10 /opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/Resources/Python.app/Contents/MacOS/Python

Content of the Python root endpoint (aka /), using curl,


  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   134  100   1 0    0     0      0      0 --:--:-- --:--:-- --:--:--     034    0     0   114k      0 --:--:-- --:--:-- --:--:--  130k
                  Dload  Upload   Total   Spent    Left  Speed
100   134  100   1 0    0     0      0      0 --:--:-- --:--:-- --:--:--     034    0     0   114k      0 --:--:-- --:--:-- --:--:--  130k



    <html>
    <head>
        <title>Hello</title>
    </head>
    <body>
        <h2>Hello, World!</h2>
    </body>
    </html>
    </h2>
    </body>
    </html>

FE GET: Reading data from Flask Server with JavaScript from BE

JavaScript is used to fetch rows of data from backend, after data is received it is formated into the HTML DOM as a table

  1. HTML is used to setup basics of a table
  2. The <script>, has javascript a fetch that passes endpoint (url) and options. The options are critical to communicating request requirements, bad options produce bad results.
  3. Data extracted and is written to the DOM. Headings are static in the code, but rows are dynamically extracted according to the information contained in the server.
%%html

<h1>FE GET method access</h1>

<p>This code extracts data "live" from a local Web Server with JavaScript fetch. Additionally, it formats the data into
  a table.</p>

<!-- Head contains information to Support the Document -->

<!-- HTML table fragment for page -->
<table id="demo" class="table">
  <thead>
    <tr>
      <th>First Name</th>
      <th>Last Name</th>
      <th>Residence</th>
    </tr>
  </thead>
  <tbody id="result">
    <!-- javascript generated data -->
  </tbody>
</table>

<script>
  { // Jupyter Notebook container to avoid variable name conflicts

    // prepare HTML result container for new output
    const resultContainer = document.getElementById("result");

    // prepare URL
    const url = "http://localhost:5001/api/data";

    // set method to GET, supporting all network options 
    const options = {
      method: 'GET', // *GET, POST, PUT, DELETE, etc.
      mode: 'cors', // no-cors, *cors, same-origin
      cache: 'default', // *default, no-cache, reload, force-cache, only-if-cached
      credentials: 'include', // include, *same-origin, omit
      headers: {
        'Content-Type': 'application/json',
      },
    };

    // fetch the API
    fetch(url, options)
      .then(response => {
        if (response.status !== 200) {
          throw new Error(`Fetch error: status ${response.status}`);
        }
        return response.json();
      })
      .then(data => {
        console.log(data); // log data to console for debugging
        for (const row of data) {
          // tr and td build out for each row
          const tr = document.createElement("tr");
          const firstname = document.createElement("td");
          const lastname = document.createElement("td");
          const residence = document.createElement("td");
          // data is specific to the API
          firstname.innerHTML = row.FirstName;
          lastname.innerHTML = row.LastName;
          residence.innerHTML = row.Residence;
          // this builds each td into tr
          tr.appendChild(firstname);
          tr.appendChild(lastname);
          tr.appendChild(residence);
          // add HTML to container
          resultContainer.appendChild(tr);
        }
      })
      .catch(error => {
        const message = `Network or fetch error: ${error}`;
        const p = document.createElement("p");
        p.innerHTML = `${message}`;
        resultContainer.appendChild(p);
      });
  } 
</script>

FE GET method access

This code extracts data "live" from a local Web Server with JavaScript fetch. Additionally, it formats the data into a table.

First Name Last Name Residence

FE POST: Add data from Flask Server with JavaScript to BE

This example allows you to accept input from user and add it to the InfoDB.

  • Enter a first name, last name, and residence, then click ‘Add User’ to submit.
  • The form sends a POST request to the Flask API and updates the InfoDB in memory.
  • Re-run the data table above to see your new entry appear.
  • Data is persistent only while the server is running; stopping the server will clear the InfoDB to code defaults.
  • For true persistence, a database is needed instead of an in-memory array.
%%html

<h1>FE POST new user</h1>

<form id="addUserForm">
  <label for="firstName">First Name:</label>
  <input type="text" id="firstName" name="firstName" required><br>
  <label for="lastName">Last Name:</label>
  <input type="text" id="lastName" name="lastName" required><br>
  <label for="residence">Residence:</label>
  <input type="text" id="residence" name="residence" required><br>
  <button type="submit">Add User</button>
</form>
<div id="addUserResult"></div>

<script>
  // Handle form submission action
  document.getElementById('addUserForm').addEventListener('submit', function (event) {
    event.preventDefault();
    // gather form data
    const firstName = document.getElementById('firstName').value;
    const lastName = document.getElementById('lastName').value;
    const residence = document.getElementById('residence').value;
    // prepare data object in JSON format
    const data = {
      FirstName: firstName,
      LastName: lastName,
      Residence: residence
    };

    // prepare fetch payload
    const url = 'http://localhost:5001/api/data';
    const options = {
      method: 'POST',
      mode: 'cors',
      cache: 'default',
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data) 
    };

    // perform fetch to add user
    fetch(url, options)
      // Always extract JSON, then handle error or success
      .then(response => response.json()
        .then(json => {
          if (!response.ok) {
            throw new Error(json.message || json.error || `Fetch error: status ${response.status}`);
          }
          return json.body; // User details are wrpped in body
        })
        // on success, show user added through fetch
        .then(user => {
          const message = `Added ID ${user.id}: ${user.FirstName} ${user.LastName} from ${user.Residence}`;
          document.getElementById('addUserResult').innerHTML = `<span style="color:green">${message}</span>`;
        }))
      // on failure, catch the fetch error and display message
      .catch(error => {
        document.getElementById('addUserResult').innerHTML = `<span style='color:red'>${error}</span>`;
        console.error(error);
      });
  });
</script>

FE POST new user




Stop the Python/Flask process

This script ends Python/Flask process using pipes to obtain the python process. Then echo the python process to kill -9.

%%script bash

python_ps=$(lsof -i :5001 | awk '/Python/ {print $2}')
echo "Killing python process with PID: $python_ps"
echo $python_ps | xargs kill -9
Killing python process with PID: