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 machine.

  • 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 a web application restart. Everything 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 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 include some interactive steps 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 a pages notebook 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

WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0xf62eef92c8f0>: Failed to establish a new connection: [Errno -3] Temporary failure in name resolution')': /simple/flask/
WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0xf62eefb86ab0>: Failed to establish a new connection: [Errno -3] Temporary failure in name resolution')': /simple/flask/
Collecting flask
  Using cached flask-3.1.2-py3-none-any.whl.metadata (3.2 kB)
Collecting blinker>=1.9.0 (from flask)
  Using cached blinker-1.9.0-py3-none-any.whl.metadata (1.6 kB)
Requirement already satisfied: click>=8.1.3 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask) (8.3.0)
Collecting itsdangerous>=2.2.0 (from flask)
  Using cached itsdangerous-2.2.0-py3-none-any.whl.metadata (1.9 kB)
Requirement already satisfied: jinja2>=3.1.2 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask) (3.1.6)
Requirement already satisfied: markupsafe>=2.1.1 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask) (3.0.3)
Collecting werkzeug>=3.1.0 (from flask)
  Using cached werkzeug-3.1.3-py3-none-any.whl.metadata (3.7 kB)
Using cached flask-3.1.2-py3-none-any.whl (103 kB)
Using cached blinker-1.9.0-py3-none-any.whl (8.5 kB)
Using cached itsdangerous-2.2.0-py3-none-any.whl (16 kB)
Using cached werkzeug-3.1.3-py3-none-any.whl (224 kB)
Installing collected packages: werkzeug, itsdangerous, blinker, flask
Successfully installed blinker-1.9.0 flask-3.1.2 itsdangerous-2.2.0 werkzeug-3.1.3
Collecting flask_cors
  Using cached flask_cors-6.0.1-py3-none-any.whl.metadata (5.3 kB)
Requirement already satisfied: flask>=0.9 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask_cors) (3.1.2)
Requirement already satisfied: Werkzeug>=0.7 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask_cors) (3.1.3)
Requirement already satisfied: blinker>=1.9.0 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask>=0.9->flask_cors) (1.9.0)
Requirement already satisfied: click>=8.1.3 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask>=0.9->flask_cors) (8.3.0)
Requirement already satisfied: itsdangerous>=2.2.0 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask>=0.9->flask_cors) (2.2.0)
Requirement already satisfied: jinja2>=3.1.2 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask>=0.9->flask_cors) (3.1.6)
Requirement already satisfied: markupsafe>=2.1.1 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask>=0.9->flask_cors) (3.0.3)
Using cached flask_cors-6.0.1-py3-none-any.whl (13 kB)
Installing collected packages: flask_cors
Successfully installed flask_cors-6.0.1
Collecting flask_restful
  Using cached Flask_RESTful-0.3.10-py2.py3-none-any.whl.metadata (1.0 kB)
Collecting aniso8601>=0.82 (from flask_restful)
  Using cached aniso8601-10.0.1-py2.py3-none-any.whl.metadata (23 kB)
Requirement already satisfied: Flask>=0.8 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask_restful) (3.1.2)
Requirement already satisfied: six>=1.3.0 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask_restful) (1.17.0)
Requirement already satisfied: pytz in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from flask_restful) (2025.2)
Requirement already satisfied: blinker>=1.9.0 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from Flask>=0.8->flask_restful) (1.9.0)
Requirement already satisfied: click>=8.1.3 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from Flask>=0.8->flask_restful) (8.3.0)
Requirement already satisfied: itsdangerous>=2.2.0 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from Flask>=0.8->flask_restful) (2.2.0)
Requirement already satisfied: jinja2>=3.1.2 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from Flask>=0.8->flask_restful) (3.1.6)
Requirement already satisfied: markupsafe>=2.1.1 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from Flask>=0.8->flask_restful) (3.0.3)
Requirement already satisfied: werkzeug>=3.1.0 in /home/whitelunarium/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages (from Flask>=0.8->flask_restful) (3.1.3)
Using cached Flask_RESTful-0.3.10-py2.py3-none-any.whl (26 kB)
Using cached aniso8601-10.0.1-py2.py3-none-any.whl (52 kB)
Installing collected packages: aniso8601, flask_restful
Successfully installed aniso8601-10.0.1 flask_restful-0.3.10

Creating a 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

app = Flask(__name__)
CORS(app, supports_credentials=True, origins='*')

api = Api(app)

# --- Model class for InfoDb with CRUD naming ---
class InfoModel:
    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"]
            }
        ]

    def read(self):
        return self.data

    def create(self, entry):
        self.data.append(entry)

# Instantiate the model
info_model = InfoModel()

# --- API Resource ---
class DataAPI(Resource):
    def get(self):
        return jsonify(info_model.read())

    def post(self):
        # Add a new entry to InfoDb
        entry = request.get_json()
        if not entry:
            return {"error": "No data provided"}, 400
        info_model.create(entry)
        return {"message": "Entry added successfully", "entry": entry}, 201

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)

Explore the Python/Flask app with 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  496790 whitelunarium    3u  IPv4 8877905      0t0  TCP localhost:5001 (LISTEN)

Python process


your 131072x1 screen size is bogus. expect trouble


    PID TTY          TIME CMD
    524 pts/2    00:00:00 sh
    525 pts/2    00:00:00 sh
    531 pts/2    00:00:00 sh
    535 pts/2    00:08:54 node
    632 pts/2    00:16:35 node
 283610 pts/2    00:00:34 node
 283621 pts/2    00:06:56 node
 284658 pts/2    00:00:03 pet
 286670 pts/2    00:00:06 python
 287242 pts/2    00:01:06 node
 287330 pts/2    00:00:09 node
 343612 pts/2    00:00:07 node
 344439 pts/2    00:00:04 node
 344628 pts/2    00:00:04 node
 344851 pts/2    00:00:01 node
 344857 pts/2    00:00:01 node
 344865 pts/2    00:00:00 node
 346539 pts/2    00:00:13 node
 478599 pts/2    00:00:08 node
 478613 pts/2    00:18:17 node
 478758 pts/2    00:00:02 node
 478782 pts/2    00:00:02 pet
 479765 pts/2    00:00:15 node
 488133 pts/2    00:00:02 node
 496790 pts/2    00:00:07 python
 501312 pts/2    00:02:29 node
 501342 pts/2    00:00:09 node
 501452 pts/2    00:00:02 pet
 501977 pts/2    00:00:02 python
 502549 pts/2    00:00:25 node
 508834 pts/2    00:22:07 node
 508894 pts/2    00:00:03 pet
 508896 pts/2    00:00:02 node
 509917 pts/2    00:00:11 node
 674593 pts/2    00:00:00 bash
 674597 pts/2    00:00:00 xargs
 674598 pts/2    00:00:00 ps

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   134    0     0   2239      0 --:--:-- --:--:-- --:--:--  2271



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

Accessing Flask Server with JavaScript

JavaScript is used to fetch data from the backend. After data is received, it is formatted into the HTML DOM.

  1. HTML is used to set up the basics of a table.
  2. The <script> contains JavaScript with a fetch that passes the endpoint (URL) and options. The options are critical to communicating request requirements; bad options produce bad results.
  3. Data is extracted and 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>Flask app access using JavaScript</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
  url = "http://localhost:5001/api/data";

  // set options for cross origin header request
  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)
    // response is a RESTful "promise" on any successful fetch
    .then(response => {
      // check for response errors and display
      if (response.status !== 200) {
          console.error(response.status);
          return;
      }
      // valid response will contain json data
      response.json().then(data => {
          console.log(data);
          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);
          }
      })
  })
} 
</script>

Flask app access using JavaScript

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

Add to the InfoDB Array

This example allows you to accept input from the 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

<h3>Add a New User to InfoDB</h3>
<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>
document.getElementById('addUserForm').addEventListener('submit', async function(event) {
  event.preventDefault();
  const firstName = document.getElementById('firstName').value;
  const lastName = document.getElementById('lastName').value;
  const residence = document.getElementById('residence').value;
  const data = {
    FirstName: firstName,
    LastName: lastName,
    Residence: residence
  };
  try {
    const response = await fetch('http://localhost:5001/api/data', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    const result = await response.json();
    if (response.ok) {
      document.getElementById('addUserResult').innerHTML = '<span style="color:green">User added!</span>';
      document.getElementById('addUserForm').reset();
    } else {
      document.getElementById('addUserResult').innerHTML = '<span style="color:red">' + (result.error || 'Error adding user') + '</span>';
    }
  } catch (err) {
    document.getElementById('addUserResult').innerHTML = '<span style="color:red">Network error</span>';
  }
});
</script>

Add a New User to InfoDB




Stop the Python/Flask process

This script ends the 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: 



Usage:
 kill [options] <pid> [...]

Options:
 <pid> [...]            send signal to every <pid> listed
 -<signal>, -s, --signal <signal>
                        specify the <signal> to be sent
 -q, --queue <value>    integer value to be sent with the signal
 -l, --list=[<signal>]  list all signal names, or convert one to a name
 -L, --table            list all signal names in a nice table

 -h, --help     display this help and exit
 -V, --version  output version information and exit

For more details see kill(1).



---------------------------------------------------------------------------

CalledProcessError                        Traceback (most recent call last)

Cell In[5], line 1
----> 1 get_ipython().run_cell_magic('script', 'bash', '\npython_ps=$(lsof -i :5001 | awk \'/Python/ {print $2}\')\necho "Killing python process with PID: $python_ps"\necho $python_ps | xargs kill -9\n')


File ~/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2565, in InteractiveShell.run_cell_magic(self, magic_name, line, cell)
   2563 with self.builtin_trap:
   2564     args = (magic_arg_s, cell)
-> 2565     result = fn(*args, **kwargs)
   2567 # The code below prevents the output from being displayed
   2568 # when using magics with decorator @output_can_be_silenced
   2569 # when the last Python token in the expression is a ';'.
   2570 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):


File ~/portfolio/Aneesh_2026/venv/lib/python3.12/site-packages/IPython/core/magics/script.py:348, in ScriptMagics.shebang(self, line, cell)
    343 if args.raise_error and p.returncode != 0:
    344     # If we get here and p.returncode is still None, we must have
    345     # killed it but not yet seen its return code. We don't wait for it,
    346     # in case it's stuck in uninterruptible sleep. -9 = SIGKILL
    347     rc = p.returncode or -9
--> 348     raise CalledProcessError(rc, cell)


CalledProcessError: Command 'b'\npython_ps=$(lsof -i :5001 | awk \'/Python/ {print $2}\')\necho "Killing python process with PID: $python_ps"\necho $python_ps | xargs kill -9\n'' returned non-zero exit status 123.