Introduction

Frontend and backend are two essential components of a web application. The frontend is the part of the application that interacts with the user, whereas the backend is the part that handles the logic and data processing behind the scenes.

The frontend, also known as the client-side, typically consists of HTML, CSS, and JavaScript code that runs in the user's web browser. The frontend handles the user interface, page layout, and overall look of the application. It also handles user interactions, such as submitting forms, clicking buttons, and navigating between pages.

On the other hand, the backend, also known as the server-side, typically consists of a server, a database, and, in our case, APIs. The backend handles the processing and storage of data, manages user authentication and authorization, and handles business logic and rules. The backend also communicates with the frontend, providing the necessary data to render the user interface and processing user inputs.

Backend

In our class we mainly use Python and SQL/JSON to create APIs and databases. Here is a simple example of creating a SQL database and using CRUD as well.

What is CRUD

  • C: The 'C' stands for create, meaning to create a new entry in a database. In this case, creating a new entry about a certain movie or TV show.

  • R: Read, or to retrieve data from the database. In this case it is selecting the movie/TV shwo that you choose to display.

  • U: Update, or changing an existing entry in the database. In this case it is selecting the preexisting movie/TV show and changing the values to match what you want.

  • D: Delete, or removing data from the database. In this case it is selecting the preexisting movie/TV show and removing the entry from the database.

Films API

This API is intended to be used as a list of movies and TV shows that a person has watched. It includes attributes for the Film name(key), the year released, the language, the number of episodes, A list of the number of episodes(using pickletype), and a youtube url for the trailer. The CRUD works as follows: Create: Enter the above mentioned attributes Read: Returns all of the films and their attributes Update: Takes in new episodes watched, and a list of their names, and adds them to their respective attibutes Delete: Option for deleting every film, also takes in a name to delete that film if it exists

from flask import Flask
import sqlite3

app = Flask(__name__)
# Connect to the SQLite database using SQLite3
conn = sqlite3.connect('films.db')

# Create a cursor object to execute SQL commands
cursor = conn.cursor()

# Create a table in the database
cursor.execute('''CREATE TABLE movies2
                 (id INTEGER PRIMARY KEY, title TEXT, year INTEGER, epcount INTEGER, language TEXT, trailer TEXT, eplist TEXT, genre TEXT)''')

# Commit the changes to the database and close the connection
conn.commit()
conn.close()
import sqlite3

def create():
    # Ask the user for movie details
    title = input("Enter the movie/tv show title: ")
    year = input("Enter the movie/tv show release year: ")
    epcount = input("Enter the movie/tv show epcount: ")
    language = input("Enter the movie/tv show language: ")
    eplist = input("Enter the movie/tv show episode names: ")
    trailer = input("Enter the link movie/tv show trailer: ")
    genre = input("Enter the link movie/tv show genre: ")

    # Connect to the database and create a cursor to execute SQL commands
    database = 'films.db'
    connection = sqlite3.connect(database)
    cursor = connection.cursor()

    try:
        # Execute SQL to insert record into db
        cursor.execute("INSERT INTO movies2 (title, year, epcount, language, eplist, trailer, genre) VALUES (?, ?, ?, ?, ?, ?, ?)", (title, year, epcount, language, eplist, trailer, genre))
        # Commit the changes
        connection.commit()
        print(f"{title} has been added to the list of movies.")


    except sqlite3.Error as error:
        print("Error while inserting record:", error)

    # Close cursor and connection
    cursor.close()
    connection.close()

create()
20th Century Girl has been added to the list of movies.
def read(id):
    # Connect to the database and create a cursor to execute SQL commands
    database = 'films.db'
    connection = sqlite3.connect(database)
    cursor = connection.cursor()

    # Execute SQL to select a record from db by id
    cursor.execute("SELECT * FROM movies2 WHERE id=?", (id,))

    # Fetch the record from the cursor
    movie = cursor.fetchone()

    # If movie exists, print its details, else print message
    if movie:
        print(f"{movie[0]}. {movie[1]}, {movie[2]}, {movie[3]}, {movie[4]}, {movie[5]}, {movie[6]}, {movie[7]}")
    else:
        print("Movie not found.")

    # Close cursor and connection
    cursor.close()
    connection.close()

read(id=1)
1. 20th Century Girl, 2022, 1, Korean, N/A, 20th Century Girl, Romance
def update(id):
    # Connect to the database and create a cursor to execute SQL commands
    database = 'films.db'
    connection = sqlite3.connect(database)
    cursor = connection.cursor()

    # Ask the user for movie details to update
    title = input("Enter the updated movie/tv show title: ")
    year = input("Enter the updated movie/tv show release year: ")
    epcount = input("Enter the updated movie/tv show epcount: ")
    language = input("Enter the updated movie/tv show language: ")
    eplist = input("Enter the updated movie/tv show episode names: ")
    trailer = input("Enter the updated link movie/tv show trailer: ")
    genre = input("Enter the updated link movie/tv show genre: ")

    try:
        # Execute SQL to update the record in db
        cursor.execute("UPDATE movies2 SET title=?, year=?, epcount=?, language=?, eplist=?, trailer=?, genre=? WHERE id=?", (title, year, epcount, language, eplist, trailer, genre, id))
        # Commit the changes
        connection.commit()
        print("Movie updated successfully.")

    except sqlite3.Error as error:
        print("Error while updating record:", error)

    # Close cursor and connection
    cursor.close()
    connection.close()

update(id=1)
Movie updated successfully.
def delete(id):
    # Connect to the database and create a cursor to execute SQL commands
    database = 'films.db'
    connection = sqlite3.connect(database)
    cursor = connection.cursor()

    try:
        # Execute SQL to delete the record from db by id
        cursor.execute("DELETE FROM movies2 WHERE id=?", (id,))
        # Commit the changes
        connection.commit()
        print("Movie deleted successfully.")

    except sqlite3.Error as error:
        print("Error while deleting record:", error)

    # Close cursor and connection
    cursor.close()
    connection.close()

delete(id=2)
Movie deleted successfully.

Fetching

Overview

  • Involves retrieving data from a server or database
  • Can use different HTTP methods, such as GET, POST, PUT, and DELETE, to perform different types of operations on the server.
  • Fetching can be done through a variety of ways including AJAX, XHR, and Axios
  • In APCSP we tend to use the Fetch API over anything else
  • Fetching involves sending a request to a server using a URL (Uniform Resource Locator), which identifies the location of the resource being requested.
  • Can receive data in various formats, including JSON
  • JSON data can be parsed into objects and arrays in JavaScript, making it easy to work with and manipulate in the frontend

Python Fetch Using Request

import requests

url = "https://moviesdatabase.p.rapidapi.com/titles"

headers = {
	"content-type": "application/octet-stream",
	"X-RapidAPI-Key": "8401db6433msh3a46dd5bf23ad2ep19a280jsn48536a994246",
	"X-RapidAPI-Host": "moviesdatabase.p.rapidapi.com"
}

response = requests.get(url, headers=headers)

print(response.json())
{'page': 1, 'next': '/titles?page=2', 'entries': 10, 'results': [{'id': 'tt0001922', 'primaryImage': {'id': 'rm736959488', 'width': 800, 'height': 563, 'url': 'https://m.media-amazon.com/images/M/MV5BZDI4MmJiMmMtMzQ3Mi00N2Y0LTlkYmUtYmQ0ZTQ1NzVlZmVjXkEyXkFqcGdeQXVyMDUyOTUyNQ@@._V1_.jpg', 'caption': {'plainText': 'Darwin Karr and Gertrude McCoy in That Winsome Winnie Smile (1911)', '__typename': 'Markdown'}, '__typename': 'Image'}, 'titleType': {'text': 'Short', 'id': 'short', 'isSeries': False, 'isEpisode': False, '__typename': 'TitleType'}, 'titleText': {'text': 'That Winsome Winnie Smile', '__typename': 'TitleText'}, 'releaseYear': {'year': 1911, 'endYear': None, '__typename': 'YearRange'}, 'releaseDate': {'day': 9, 'month': 9, 'year': 1911, '__typename': 'ReleaseDate'}}, {'id': 'tt0001539', 'primaryImage': {'id': 'rm1311052544', 'width': 800, 'height': 582, 'url': 'https://m.media-amazon.com/images/M/MV5BZGY5NzI0MzQtM2EwYi00NzY2LThiYjYtYTM5YmViZDEwMzkzXkEyXkFqcGdeQXVyMDUyOTUyNQ@@._V1_.jpg', 'caption': {'plainText': 'Caïn et Abel (1911)', '__typename': 'Markdown'}, '__typename': 'Image'}, 'titleType': {'text': 'Short', 'id': 'short', 'isSeries': False, 'isEpisode': False, '__typename': 'TitleType'}, 'titleText': {'text': 'Caïn et Abel', '__typename': 'TitleText'}, 'releaseYear': {'year': 1911, 'endYear': None, '__typename': 'YearRange'}, 'releaseDate': {'day': None, 'month': None, 'year': 1911, '__typename': 'ReleaseDate'}}, {'id': 'tt0001636', 'primaryImage': None, 'titleType': {'text': 'Short', 'id': 'short', 'isSeries': False, 'isEpisode': False, '__typename': 'TitleType'}, 'titleText': {'text': 'Galileo', '__typename': 'TitleText'}, 'releaseYear': {'year': 1911, 'endYear': None, '__typename': 'YearRange'}, 'releaseDate': {'day': None, 'month': 7, 'year': 1911, '__typename': 'ReleaseDate'}}, {'id': 'tt0002148', 'primaryImage': {'id': 'rm1303852544', 'width': 700, 'height': 526, 'url': 'https://m.media-amazon.com/images/M/MV5BMWY2ODg0YWEtZDVmYy00OTEwLTkxN2YtYzY5ZmRmNjVlZWYyXkEyXkFqcGdeQXVyMDUyOTUyNQ@@._V1_.jpg', 'caption': {'plainText': "Fred Mace and Mabel Normand in The Drummer's Vacation (1912)", '__typename': 'Markdown'}, '__typename': 'Image'}, 'titleType': {'text': 'Short', 'id': 'short', 'isSeries': False, 'isEpisode': False, '__typename': 'TitleType'}, 'titleText': {'text': "The Drummer's Vacation", '__typename': 'TitleText'}, 'releaseYear': {'year': 1912, 'endYear': None, '__typename': 'YearRange'}, 'releaseDate': {'day': 23, 'month': 12, 'year': 1912, '__typename': 'ReleaseDate'}}, {'id': 'tt0001702', 'primaryImage': None, 'titleType': {'text': 'Short', 'id': 'short', 'isSeries': False, 'isEpisode': False, '__typename': 'TitleType'}, 'titleText': {'text': "The Indian Maiden's Lesson", '__typename': 'TitleText'}, 'releaseYear': {'year': 1911, 'endYear': None, '__typename': 'YearRange'}, 'releaseDate': {'day': 22, 'month': 4, 'year': 1911, '__typename': 'ReleaseDate'}}, {'id': 'tt0001856', 'primaryImage': {'id': 'rm970923264', 'width': 800, 'height': 597, 'url': 'https://m.media-amazon.com/images/M/MV5BYmVhNGZlZTEtNjFmMS00MjEyLThkZmMtMTIwZjRjNzFkYjU3XkEyXkFqcGdeQXVyMDUyOTUyNQ@@._V1_.jpg', 'caption': {'plainText': 'Edwin August and Dorothy West in The Revenue Man and the Girl (1911)', '__typename': 'Markdown'}, '__typename': 'Image'}, 'titleType': {'text': 'Short', 'id': 'short', 'isSeries': False, 'isEpisode': False, '__typename': 'TitleType'}, 'titleText': {'text': 'The Revenue Man and the Girl', '__typename': 'TitleText'}, 'releaseYear': {'year': 1911, 'endYear': None, '__typename': 'YearRange'}, 'releaseDate': {'day': 25, 'month': 9, 'year': 1911, '__typename': 'ReleaseDate'}}, {'id': 'tt0001790', 'primaryImage': {'id': 'rm635370240', 'width': 1748, 'height': 1340, 'url': 'https://m.media-amazon.com/images/M/MV5BMjAzMzQ3MjQxOV5BMl5BanBnXkFtZTgwMDQzNzExMzE@._V1_.jpg', 'caption': {'plainText': 'Eugénie Nau in Les misérables - Époque 1: Jean Valjean (1913)', '__typename': 'Markdown'}, '__typename': 'Image'}, 'titleType': {'text': 'Movie', 'id': 'movie', 'isSeries': False, 'isEpisode': False, '__typename': 'TitleType'}, 'titleText': {'text': 'Les misérables - Époque 1: Jean Valjean', '__typename': 'TitleText'}, 'releaseYear': {'year': 1913, 'endYear': None, '__typename': 'YearRange'}, 'releaseDate': {'day': 3, 'month': 1, 'year': 1913, '__typename': 'ReleaseDate'}}, {'id': 'tt0000543', 'primaryImage': None, 'titleType': {'text': 'Short', 'id': 'short', 'isSeries': False, 'isEpisode': False, '__typename': 'TitleType'}, 'titleText': {'text': 'Christian IXS bisættelse', '__typename': 'TitleText'}, 'releaseYear': {'year': 1906, 'endYear': None, '__typename': 'YearRange'}, 'releaseDate': {'day': 19, 'month': 2, 'year': 1906, '__typename': 'ReleaseDate'}}, {'id': 'tt0002089', 'primaryImage': None, 'titleType': {'text': 'Movie', 'id': 'movie', 'isSeries': False, 'isEpisode': False, '__typename': 'TitleType'}, 'titleText': {'text': 'Carmen', '__typename': 'TitleText'}, 'releaseYear': {'year': 1912, 'endYear': None, '__typename': 'YearRange'}, 'releaseDate': None}, {'id': 'tt0000548', 'primaryImage': None, 'titleType': {'text': 'Short', 'id': 'short', 'isSeries': False, 'isEpisode': False, '__typename': 'TitleType'}, 'titleText': {'text': 'Fiskerliv i Norden', '__typename': 'TitleText'}, 'releaseYear': {'year': 1906, 'endYear': None, '__typename': 'YearRange'}, 'releaseDate': {'day': 15, 'month': 9, 'year': 1906, '__typename': 'ReleaseDate'}}]}

This is a functional fetch of a movies API from Rapid API, but the data isn't very readable. Below is an example of using Pandas to format the key values as a dataframe.

import requests
import pandas as pd

url = "https://moviesdatabase.p.rapidapi.com/titles"

headers = {
    "content-type": "application/octet-stream",
    "X-RapidAPI-Key": "8401db6433msh3a46dd5bf23ad2ep19a280jsn48536a994246",
    "X-RapidAPI-Host": "moviesdatabase.p.rapidapi.com"
}

response = requests.get(url, headers=headers)
data = response.json()

# Create an empty DataFrame
df = pd.DataFrame()

# Extract the required information and store it in a list of dictionaries
results = data["results"]
entries = []
for result in results:
    entry = {
        "id": result["id"],
        "title": result["titleText"]["text"],
        "release_year": result["releaseYear"]["year"],
    }
    entries.append(entry)

# Convert the list of dictionaries into a DataFrame
df = pd.DataFrame(entries)

# ADD YOUR OWN COLUMN TO THE DATAFRAME, added isSeries
results = data["results"]
entries = []
for result in results:
    entry = {
        "id": result["id"],
        "title": result["titleText"]["text"],
        "release_year": result["releaseYear"]["year"],
        "isSeries": result['titleType']['isSeries']
    }
    entries.append(entry)

df = pd.DataFrame(entries)

print(df)
          id                                    title  release_year  isSeries
0  tt0001922                That Winsome Winnie Smile          1911     False
1  tt0001539                             Caïn et Abel          1911     False
2  tt0001636                                  Galileo          1911     False
3  tt0002148                   The Drummer's Vacation          1912     False
4  tt0001702               The Indian Maiden's Lesson          1911     False
5  tt0001856             The Revenue Man and the Girl          1911     False
6  tt0001790  Les misérables - Époque 1: Jean Valjean          1913     False
7  tt0000543                 Christian IXS bisættelse          1906     False
8  tt0002089                                   Carmen          1912     False
9  tt0000548                       Fiskerliv i Norden          1906     False

Using Pandas to format a request obtained from a 3rd Party API makes it much easier to read and you can select what you want to display as well. Pandas makes it easy to access data that you feel is important.

Backend and Frontend Example

Write notes below

  • CRUD (Create, Read, Update, Delete) is backend
  • Data cleaning in both frontend and backend
  • Data cleaning especially important in backend to ensure server doesn't break
  • Keep note of datatypes in SQLite3 tables
  • Attribute context is important to makesure backend databse is correct, for example, years in a correct timeframe

Hacks

  1. Create a completely unique API with all 4 CRUD features (Create, Read, Update, Delete)
  2. Create a Fetch API request for your corresponding API
  3. Attempt a complete website on GitHub Pages including HTML

See my part of the wesbite - Click here Code below is for Wordle leaderboard

Video proof of frontend and backend connection, START AT 0:39 -Click Here

Unique API and CRUD

Model file

""" database dependencies to support sqliteDB examples """
from random import randrange
from datetime import date
import os, base64
import json

from __init__ import app, db
from sqlalchemy.exc import IntegrityError


''' Tutorial: https://www.sqlalchemy.org/library.html#tutorials, try to get into Python shell and follow along '''

# Define the User class to manage actions in the 'users' table
# -- Object Relational Mapping (ORM) is the key concept of SQLAlchemy
# -- a.) db.Model is like an inner layer of the onion in ORM
# -- b.) User represents data we want to store, something that is built on db.Model
# -- c.) SQLAlchemy ORM is layer on top of SQLAlchemy Core, then SQLAlchemy engine, SQL
class Wordle(db.Model):
    __tablename__ = 'wordles'  # table name is plural, class name is singular

    # Define the User schema with "vars" from object
    id = db.Column(db.Integer, primary_key=True)
    _name = db.Column(db.String(255), unique=False, nullable=False)
    #_uid = db.Column(db.String(255), unique=True, nullable=False)
    _score = db.Column(db.String(255), unique=False, nullable=False)
    _pin = db.Column(db.String(255), unique=False, nullable=False)

    # constructor of a User object, initializes the instance variables within object (self) 
    def __init__(self, name, score, pin):
        self._name = name
        self._score = score
        self._pin = pin
    
    #here's the name getter
    @property
    def name(self):
        return self._name

    #here's the name setter
    @name.setter
    def name(self, name):
        self._name = name
    
    #here's the score getter
    @property
    def score(self):
        return self._score
    
    # score setter
    @score.setter
    def score(self, score):
        self._score = score
    
    #here's the pin getter
    @property
    def pin(self):
        return self._pin
    
    # pin setter
    @pin.setter
    def pin(self, pin):
        self._pin = pin
    
    # output content using str(object) in human readable form, uses getter
    # output content using json dumps, this is ready for API response
    def __str__(self):
        return json.dumps(self.read())

    # CRUD create/add a new record to the table
    # returns self or None on error
    def create(self):
        try:
            # creates a person object from User(db.Model) class, passes initializers
            db.session.add(self)  # add prepares to persist person object to Users table
            db.session.commit()  # SqlAlchemy "unit of work pattern" requires a manual commit
            return self
        except IntegrityError:
            db.session.remove()
            return None

    # CRUD read converts self to dictionary
    # returns dictionary
    def read(self):
        return {
            "id": self.id,
            "name": self.name,
            "score": self.score,
            "pin": self.pin
        }

    # CRUD update: updates user name, password, phone
    # returns self
    def update(self, name="", score="", pin=""):
        """only updates values with length"""
        if len(name) > 0:
            self.name = name
        if len(score) > 0:
            self.score = score
        if len(pin) > 0:
            self.pin = pin
        db.session.commit()
        return self

    # CRUD delete: remove self
    # None
    def delete(self):
        db.session.delete(self)
        db.session.commit()
        return None


"""Database Creation and Testing """


# Builds working data for testing
def initWordles():
    with app.app_context():
        """Create database and tables"""
        db.create_all()
        """Tester data for table
        w1 = Wordle(name="Thomas Edison", score=12, pin="qwerty123")
        w2 = Wordle(name="John Mortensen", score=15, pin="codec0decod3bro")
        w3 = Wordle(name="Karl Giant", score=10, pin="i_am-the-f4th3r")
        
        wordles = [w1, w2, w3]
        #Builds sample wordles data
        for wordle in wordles:
            try:
                wordle.create()
            except IntegrityError:
                '''fails with bad or duplicate data'''
                db.session.remove()
                print(f"Records exist, duplicate data, or error: {wordle.name}")
        """

def deleteID(user_id):                
    user = Wordle.query.get(user_id)

    #user = Wordle.query.filter_by(name=name).first()
    if user != None:
        print("Query 1:", user)
        db.session.delete(user)
        db.session.commit() 
        return True
    else:
        print("user "+str(user_id)+" not found")
        return False


def deleteID(user_id, user_pin):                
    user = Wordle.query.get(user_id)

    #user = Wordle.query.filter_by(name=name).first()
    if user != None:
        print("Query 1:", user)
        real_user_pin = user.pin
        print(real_user_pin)
        if user_pin == real_user_pin:
            db.session.delete(user)
            db.session.commit() 
            return True
        else:
            print("User "+str(user_id)+" not found, or pin not correct.")
            return False

Explanation

The Wordle class first has the names for the SQLite table columns, id, name, score, and pin. It also has the contructor init function, which is automaticallyed called when object of class is created to initalize the attributes of the object.

class Wordle(db.Model):
    __tablename__ = 'wordles'  # table name is plural, class name is singular

    # Define the User schema with "vars" from object
    id = db.Column(db.Integer, primary_key=True)
    _name = db.Column(db.String(255), unique=False, nullable=False)
    #_uid = db.Column(db.String(255), unique=True, nullable=False)
    _score = db.Column(db.String(255), unique=False, nullable=False)
    _pin = db.Column(db.String(255), unique=False, nullable=False)

    # constructor of a User object, initializes the instance variables within object (self) 
    def __init__(self, name, score, pin):
        self._name = name
        self._score = score
        self._pin = pin

Next, there are several getter and setter functions for each of the attributes above, name, score and pin. The setter method that lets you set or change the value of attribute in a class, and the getter method that lets you access attrbute in a given class.

@property
def name(self):
    return self._name

#here's the name setter
@name.setter
def name(self, name):
    self._name = name

Next are all the property decorators for each of the CRUD Functions

The create function adds the Wordle object to the specified database in the SQLite session, and commits the addition.

def create(self):
    try:
        # creates a person object from User(db.Model) class, passes initializers
        db.session.add(self)  # add prepares to persist person object to Users table
        db.session.commit()  # SqlAlchemy "unit of work pattern" requires a manual commit
        return self
    except IntegrityError:
        db.session.remove()
        return None

The read function returns all of the Wordle object's attributes in JSON so it is readable, hence the name.

def read(self):
    return {
        "id": self.id,
        "name": self.name,
        "score": self.score,
         "pin": self.pin
     }

The update function allows the user to change/alter each of the attributes. It checks if the users changes are valid before. Then it commits the changes.

def update(self, name="", score="", pin=""):
    """only updates values with length"""
    if len(name) > 0:
        self.name = name
    if len(score) > 0:
        self.score = score
    if len(pin) > 0:
        self.pin = pin
    db.session.commit()
    return self

The delete function allows the user to delete a row, then commits their deletion.

def delete(self):
    db.session.delete(self)
    db.session.commit()
    return None

API File

from flask import Blueprint, request, jsonify
from flask_restful import Api, Resource # used for REST API building
from datetime import *

from model.wordles import Wordle
from model.wordles import deleteID


wordle_api = Blueprint('wordle_api', __name__,
                   url_prefix='/api/wordles')

# API docs https://flask-restful.readthedocs.io/en/latest/api.html
api = Api(wordle_api)

class WordleAPI:        
    class _Create(Resource):
        def post(self):
            ''' Read data for json body '''
            body = request.get_json()
            
            ''' Avoid garbage in, error checking '''

            # validate name
            name = body.get('name')
            if name is None or len(name) < 1:
                return {'message': f'Name is missing, or is less than 1 character'}, 210
            # validate pin
            pin = body.get('pin')
            if pin is None or len(pin) < 2:
                return {'message': f'Pin is missing, or is less than 2 characters'}, 220
            #validate score
            score = body.get('score')
            if score=="" or int(score)<1 or int(score)>6:
                return {'message': f'Score is missing, or is not within range (1-6, inclusive).'}, 230

            ''' #1: Key code block, setup USER OBJECT '''
            wo = Wordle(name=name, 
                      score=score,
                      pin=pin)
            
            ''' #2: Key Code block to add user to database '''
            # create user in database
            wordle = wo.create()
            # success returns json of user
            if wordle:
                return jsonify(wordle.read())
            # failure returns error
            return {'message': f'Processed {name}, either a format error or name {name} is duplicate'}, 210

    class _Read(Resource):
        def get(self):
            wordles = Wordle.query.all()    # read/extract all users from database
            json_ready = [wordle.read() for wordle in wordles]  # prepare output in json
            return jsonify(json_ready)  # jsonify creates Flask response object, more specific to APIs than json.dumps
    
    class _Update(Resource):
        def update(self):
            body = request.get_json()
            wordle_id = body.get('id')
            wu = Wordle.query.get(wordle_id)
            if wu == None:
                return {'message': f'User with id {wordle_id} could not be found.'}

            ''' Avoid garbage in, error checking '''
            # validate name
            name = body.get('name')
            if name is None or len(name) < 1:
                return {'message': f'Name is missing, or is less than 1 character'}, 210
            # validate pin
            pin = body.get('pin')
            if pin is None or len(pin) < 2:
                return {'message': f'Pin is missing, or is less than 2 characters'}, 220
            #validate score
            score = body.get('score')
            if score=="" or int(score)<1 or int(score)>6:
                return {'message': f'Score is missing, or is not within range (1-6, inclusive).'}, 230


            status = wu.update(self, name=name, pin=pin, score=score)
            if status != None:
                return {'message': f'Successfully updated user with id {wordle_id}.'}
            else:
                return {'message': f'An error occurred when updating the user.'}

    class _Delete(Resource):
        def delete(self):
            body = request.get_json()
            user_id = body.get('id')
            user_pin = body.get('pin')
           
            #status = deleteID(user_id)
            status = deleteID(user_id, user_pin)
            if status:
                return {'message': f'Successfully deleted user with id {user_id} '}
            else:
                return {'message': f'User with id {user_id} not found'}, 240

    # building RESTapi endpoint
    api.add_resource(_Create, '/create')
    api.add_resource(_Read, '/')
    api.add_resource(_Update, '/update')
    api.add_resource(_Delete, '/delete')

Explanation

Again with CRUD (see explations above). The functions above include instructions that specify how to handle the provided information when it is sent from the frontend to specific endpoints.

Fetch API Request

Explanation

In the begininng, we have the initilization of "get" fetch, using the base endpoint URLs from the code above. The purpose of the "get" fetch is to verify the input data before it is added to the database. "get" fetch can also be used to generate a table.

The function following the "read" fetch incorporates the code for a frontend "post" request. It employs the "/create" resource link to send a JSON data payload to the backend, where it is stored in the database.

  const url = "https://cgato.duckdns.org/api/wordles"
  const create_fetch = url + '/create';
  const read_fetch = url + '/';
  const delete_fetch = url + '/delete';

  // Load users on page entry
  read_users();

  // Display User Table, data is fetched from Backend Database
  function read_users() {
    // prepare fetch options
    const read_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: 'omit', // include, *same-origin, omit
      headers: {
        'Content-Type': 'application/json'
      },
    };

    // fetch the data from API
    fetch(read_fetch, read_options)
      // response is a RESTful "promise" on any successful fetch
      .then(response => {
        // check for response errors
        if (response.status !== 200) {
            const errorMsg = 'Database read error: ' + response.status;
            console.log(errorMsg);
            const tr = document.createElement("tr");
            const td = document.createElement("td");
            td.innerHTML = errorMsg;
            tr.appendChild(td);
            resultContainer.appendChild(tr); 
            return;
        }
        // valid response will have json data
        response.json().then(data => {
            console.log(data);
            for (let row in data) {
              console.log(data[row]);
              add_row(data[row]);
            }
        })
    })
    // catch fetch errors (ie ACCESS to server blocked)
    .catch(err => {
      console.error(err);
      const tr = document.createElement("tr");
      const td = document.createElement("td");
      td.innerHTML = err;
      tr.appendChild(td);
      resultContainer.appendChild(tr);
    });
  }

  function createUser(){
    const body = {
        name: document.getElementById("name").value,
        pin: document.getElementById("pin").value,
        score: document.getElementById("score").value
    };
    const requestOptions = {
        method: 'POST',
        body: JSON.stringify(body),
        headers: {
            "content-type": "application/json",
            'Authorization': 'Bearer my-token',
        },
    };
  

    // URL for Create API
    // Fetch API call to the database to create a new user
    fetch(create_fetch, requestOptions)
      .then(response => {
        // trap error response from Web API
        if (response.status !== 200) {
          alert("There was an error processing your form. Please make sure all fields are filled in correctly:")
          if (response.status == 210){
            alert("Name is missing, or is less than 1 character.")
          }
          if (response.status == 220){
            alert("Pin is missing, or is less than 2 characters.")
          }
          if (response.status == 230){
            alert("Score is missing, or is not within range (1-6, inclusive).")
          }

          /*
          const errorMsg = 'Database create error: ' + response.status;
          console.log(errorMsg);
          const tr = document.createElement("tr");
          const td = document.createElement("td");
          td.innerHTML = errorMsg;
          tr.appendChild(td);
          resultContainer.appendChild(tr);
          */
          return;
        }
        // response contains valid result
        response.json().then(data => {
            console.log(data);
            //add a table row for the new/created userid
            add_row(data);
        })
    })
  }

  function deleteUser(){
      //idToDelete = document.getElementById("id").value
      const body = {
        id: document.getElementById("id").value,
        pin: document.getElementById("deletepin").value
      };
      const requestOptions = {
          method: 'DELETE',
          body: JSON.stringify(body),
          headers: {
              "content-type": "application/json",
              'Authorization': 'Bearer my-token',
          },
      };

    fetch(delete_fetch, requestOptions)
      .then(response => {
        // trap error response from Web API
        if (response.status == 240){
            alert("ID not found, or user/pin incorrect. Please make sure all fields are filled in correctly.")
            return;
        }
        else{
            resultContainer.innerHTML = "";
            read_users();
            //delete_row(idToDelete);
            
        // response contains valid result
            response.json().then(data => {
                console.log(data);
            })
      }
    })
  }
    

  function add_row(data) {
    const tr = document.createElement("tr");
    const id = document.createElement("td");
    const name = document.createElement("td");
    //const pin = document.createElement("td");
    const score = document.createElement("td");
  

    // obtain data that is specific to the API
    id.innerHTML = data.id;
    name.innerHTML = data.name; 
    //pin.innerHTML = data.pin;
    score.innerHTML = data.score; 

    // add HTML to container
    tr.appendChild(id);
    tr.appendChild(name);
    //tr.appendChild(pin);
    tr.appendChild(score);

    resultContainer.appendChild(tr);
  }