Introduction

Welcome to this journey into the world of web servers and the Flask framework! In the previous weeks, you’ve successfully set up a web server using GitHub Pages, converting Jupyter Notebooks into Markdown for a seamless online presentation. Today, we’ll take that knowledge to the next level as we dive into creating your very own web server using Flask.

Understanding Web Servers

What is a Web Server?

Traditionally, we had librarians at libraries that would help you find books or information. Today in the digital world, thousands upon thousands of home pages, search engines, and digital archives have been built using web servers.

GitHub Pages vs. Flask

You’ve already experienced a form of web server through GitHub Pages. Think of GitHub Pages as a library that has established rules for publishing Markdown notes and Jupyter Notebooks neatly on a bookshelf.

Now, let’s introduce Flask, your personal web server. Flask can create and manage any type of content, including customizing everything according to your preferences, and even serve additional information (like a database with APIs).

The Flask Framework Flask is a micro web framework written in Python. It’s designed to be minimal and easy to use, making it perfect for building web applications, APIs, and, yes, even your web server. Today, we will start with the basics of Flask and see how it empowers you to create and manage web content.

Our Goals for Today

Here’s what we’ll accomplish in this session:

  • Create a minimal Flask server.
  • Explore the Python/Flask process.
  • Access data from our Flask server using Python.
  • Access data from our Flask server using JavaScript.
  • Learn how to stop the Python/Flask process gracefully.

Install required libraries

These libraries are required to run and interact with the Python web server.

!pip install flask flask-cors requests
Requirement already satisfied: flask in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (3.1.0)
Requirement already satisfied: flask-cors in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (5.0.0)
Requirement already satisfied: requests in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (2.32.3)
Requirement already satisfied: Werkzeug>=3.1 in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (from flask) (3.1.3)
Requirement already satisfied: Jinja2>=3.1.2 in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (from flask) (3.1.4)
Requirement already satisfied: itsdangerous>=2.2 in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (from flask) (2.2.0)
Requirement already satisfied: click>=8.1.3 in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (from flask) (8.1.7)
Requirement already satisfied: blinker>=1.9 in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (from flask) (1.9.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (from requests) (3.4.0)
Requirement already satisfied: idna<4,>=2.5 in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (from requests) (3.10)
Requirement already satisfied: urllib3<3,>=1.21.1 in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (from requests) (2.2.3)
Requirement already satisfied: certifi>=2017.4.17 in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (from requests) (2024.8.30)
Requirement already satisfied: MarkupSafe>=2.0 in /home/yashunix/nighthawk/yash_2025/venv/lib/python3.12/site-packages (from Jinja2>=3.1.2->flask) (3.0.1)

Start Web Server

This Python code provides a simple server with an accessible API.

Note: Jupyter magic commmand %%python --bg that follows runs the server in background. This enables us to continue interacting with the subsequent Notebook cells.

%%python --bg

from flask import Flask, jsonify
from flask_cors import CORS

# initialize a flask application (app)
app = Flask(__name__)
CORS(app, supports_credentials=True, origins='*')  # Allow all origins (*)

# ... your existing Flask

# add an api endpoint to flask app
@app.route('/api/yash')
def get_yash():
    # start a list, to be used like a information database
    InfoDb = []

    # add a row to list, an Info record
    InfoDb.append({
        "FirstName": "Yash",
        "LastName": "Parikh",
        "DOB": "July 31",
        "Residence": "Antartica",
        "Email": "yashp51875@stu.powayusd.com",
        "Owns_Cars": ["2024-McLaren-W1-HotWheels"]
    })

    return jsonify(InfoDb)

# add an HTML endpoint to flask app
@app.route('/')
def say_hello():
    html_content = """
    <html>
    <head>
        <title>Group Members</title>
    </head>
    <body>
        <h2>Group Members Data</h2>
        <table border="1" style="width: 100%; text-align: left;">
            <thead>
                <tr>
                    <th>First Name</th>
                    <th>Last Name</th>
                    <th>Date of Birth</th>
                    <th>Residence</th>
                    <th>Email</th>
                    <th>Owns Cars</th>
                </tr>
            </thead>
            <tbody>
    """
    
    # List of API endpoints
    api_endpoints = [
        '/api/yash',
    ]
    
    # Fetch data from APIs and populate the table
    for endpoint in api_endpoints:
        # Fetch data from the endpoint
        try:
            response = app.test_client().get(endpoint)
            data = response.get_json()
            if data:  # Iterate over the data if it exists
                for member in data:
                    html_content += f"""
                    <tr>
                        <td>{member['FirstName']}</td>
                        <td>{member['LastName']}</td>
                        <td>{member['DOB']}</td>
                        <td>{member['Residence']}</td>
                        <td>{member['Email']}</td>
                        <td>{', '.join(member['Owns_Cars'])}</td>
                    </tr>
                    """
        except Exception as e:
            html_content += f"<tr><td colspan='6'>Error fetching data from {endpoint}: {str(e)}</td></tr>"
    
    # Close the table and body
    html_content += """
            </tbody>
        </table>
    </body>
    </html>
    """
    
    return html_content

if __name__ == '__main__':
    # starts flask server on default port, http://127.0.0.1:3003
    app.run(port=3333)

Explore the Python/Flask process 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 :3333
# see the the Python process 
echo
echo "Python process"
lsof -i :3333 | 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:3333/


Python open files on port 5001
COMMAND    PID     USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
python  550843 yashunix    3u  IPv4 10680145      0t0  TCP localhost:3333 (LISTEN)

Python process


your 131072x1 screen size is bogus. expect trouble


    PID TTY          TIME CMD
 405297 pts/0    00:00:00 sh
 405298 pts/0    00:00:00 sh
 405303 pts/0    00:00:00 sh
 405307 pts/0    00:01:09 node
 405340 pts/0    00:00:13 node
 405369 pts/0    00:12:24 node
 405421 pts/0    00:00:43 node
 405435 pts/0    00:00:01 node
 405492 pts/0    00:00:03 pet
 405860 pts/0    00:00:05 node
 414720 pts/0    00:12:03 node
 414737 pts/0    00:00:09 node
 414835 pts/0    00:00:02 pet
 415147 pts/0    00:00:04 node
 415246 pts/0    00:00:40 node
 415257 pts/0    00:00:04 python
 444719 pts/0    00:00:01 node
 447169 pts/0    00:00:06 node
 447180 pts/0    00:09:21 node
 447277 pts/0    00:00:01 pet
 447628 pts/0    00:00:00 sqlite3-editor-
 447641 pts/0    00:00:01 node
 447705 pts/0    00:00:03 node
 550843 pts/0    00:00:01 python
 582030 pts/0    00:00:00 bash
 582037 pts/0    00:00:00 xargs
 582038 pts/0    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   949  100   949    0     0  73230      0 --:--:-- --:--:-- --:--:-- 79083



    <html>
    <head>
        <title>Group Members</title>
    </head>
    <body>
        <h2>Group Members Data</h2>
        <table border="1" style="width: 100%; text-align: left;">
            <thead>
                <tr>
                    <th>First Name</th>
                    <th>Last Name</th>
                    <th>Date of Birth</th>
                    <th>Residence</th>
                    <th>Email</th>
                    <th>Owns Cars</th>
                </tr>
            </thead>
            <tbody>
    
                    <tr>
                        <td>Yash</td>
                        <td>Parikh</td>
                        <td>July 31</td>
                        <td>Antartica</td>
                        <td>yashp51875@stu.powayusd.com</td>
                        <td>2024-McLaren-W1-HotWheels</td>
                    </tr>
                    
            </tbody>
        </table>
    </body>
    </html>

Access data from our Flask server using Python

The code block below shows alternate ways to access the Web Server.

  1. Import requests and use it to obtain response from endpoints
  2. The response is a Python object that contains status codes and data
  3. The data can be in different forms, we will be focussed on JSON responses in Full-Stack.
import requests
from IPython.display import HTML, display

# call api root endpoint (aka '/'), often called home page
response = requests.get('http://127.0.0.1:3333/')
# output response in different forms
print("Print Status Message:", response)
print("\nPrint Raw HTML:\n", response.text)
display(HTML(response.text))

# call unknown api endpoint
response = requests.get('http://127.0.0.1:3333/unknown-page')
print("Print Status Message:", response)
Print Status Message: <Response [200]>

Print Raw HTML:
 
    <html>
    <head>
        <title>Group Members</title>
    </head>
    <body>
        <h2>Group Members Data</h2>
        <table border="1" style="width: 100%; text-align: left;">
            <thead>
                <tr>
                    <th>First Name</th>
                    <th>Last Name</th>
                    <th>Date of Birth</th>
                    <th>Residence</th>
                    <th>Email</th>
                    <th>Owns Cars</th>
                </tr>
            </thead>
            <tbody>
    
                    <tr>
                        <td>Yash</td>
                        <td>Parikh</td>
                        <td>July 31</td>
                        <td>Antartica</td>
                        <td>yashp51875@stu.powayusd.com</td>
                        <td>2024-McLaren-W1-HotWheels</td>
                    </tr>
                    
            </tbody>
        </table>
    </body>
    </html>
Group Members

Group Members Data

First Name Last Name Date of Birth Residence Email Owns Cars
Yash Parikh July 31 Antartica yashp51875@stu.powayusd.com 2024-McLaren-W1-HotWheels
Print Status Message: <Response [404]>
import requests
# an api endpoint most commonly returns JSON data
response = requests.get('http://127.0.0.1:3333/api/data')
response.json()
---------------------------------------------------------------------------

JSONDecodeError                           Traceback (most recent call last)

File ~/nighthawk/yash_2025/venv/lib/python3.12/site-packages/requests/models.py:974, in Response.json(self, **kwargs)
    973 try:
--> 974     return complexjson.loads(self.text, **kwargs)
    975 except JSONDecodeError as e:
    976     # Catch JSON-related errors and raise as requests.JSONDecodeError
    977     # This aliases json.JSONDecodeError and simplejson.JSONDecodeError


File /usr/lib/python3.12/json/__init__.py:346, in loads(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    343 if (cls is None and object_hook is None and
    344         parse_int is None and parse_float is None and
    345         parse_constant is None and object_pairs_hook is None and not kw):
--> 346     return _default_decoder.decode(s)
    347 if cls is None:


File /usr/lib/python3.12/json/decoder.py:337, in JSONDecoder.decode(self, s, _w)
    333 """Return the Python representation of ``s`` (a ``str`` instance
    334 containing a JSON document).
    335 
    336 """
--> 337 obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    338 end = _w(s, end).end()


File /usr/lib/python3.12/json/decoder.py:355, in JSONDecoder.raw_decode(self, s, idx)
    354 except StopIteration as err:
--> 355     raise JSONDecodeError("Expecting value", s, err.value) from None
    356 return obj, end


JSONDecodeError: Expecting value: line 1 column 1 (char 0)


During handling of the above exception, another exception occurred:


JSONDecodeError                           Traceback (most recent call last)

Cell In[24], line 4
      2 # an api endpoint most commonly returns JSON data
      3 response = requests.get('http://127.0.0.1:3333/api/data')
----> 4 response.json()


File ~/nighthawk/yash_2025/venv/lib/python3.12/site-packages/requests/models.py:978, in Response.json(self, **kwargs)
    974     return complexjson.loads(self.text, **kwargs)
    975 except JSONDecodeError as e:
    976     # Catch JSON-related errors and raise as requests.JSONDecodeError
    977     # This aliases json.JSONDecodeError and simplejson.JSONDecodeError
--> 978     raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)


JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Access data from our Flask server using JavaScript

This sample is very similar to Full-Stack as the JavaScript is running through Jupyter and the Web server is a Python Process running on our machine (local server).

  1. HTML is used to setup basics of a table
  2. The script block, has javascript fetch that passes endpoint (url) and options. The options are critical to communicating request requirements.
  3. Similar to python examples, data is extracted and that data is written to the document, which is what is viewable to the user as the page is rendered. Headings are static in the document, but rows are dynamically extracted according to the information contained in the server.
%%html

<h1>Access data from our Flask server 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>
  // prepare HTML result container for new output
  let resultContainer = document.getElementById("result");
  
  // prepare URL
  url = "http://127.0.0.1:3333/api/data";

  // set options for cross origin header request
  let 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>


Access data from our Flask server 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

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 :3333 | 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[26], line 1
----> 1 get_ipython().run_cell_magic('script', 'bash', '\npython_ps=$(lsof -i :3333 | awk \'/Python/ {print $2}\')\necho "Killing python process with PID: $python_ps"\necho $python_ps | xargs kill -9\n')


File ~/nighthawk/yash_2025/venv/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2541, in InteractiveShell.run_cell_magic(self, magic_name, line, cell)
   2539 with self.builtin_trap:
   2540     args = (magic_arg_s, cell)
-> 2541     result = fn(*args, **kwargs)
   2543 # The code below prevents the output from being displayed
   2544 # when using magics with decorator @output_can_be_silenced
   2545 # when the last Python token in the expression is a ';'.
   2546 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):


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


CalledProcessError: Command 'b'\npython_ps=$(lsof -i :3333 | 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.

Hacks

Edit, stop and start the web server.

  • Add to the Home Page
  • Add your own information to the Web API
  • Use from Template to start your own Team Flask project https://github.com/nighthawkcoders/flocker_backend