Full Stack Create and Read operation
This describes markdown file that activates python API, this ultimately performs full stack using HTML, JavaScript, Python, SQL and return.
Frontend Runtime
Try it out, to understand how things work and observe table update after adding new record through Create. All data is coming persistently stored in backend database.
- Cross-Origin Resource Sharing. This example is sharing data between servers.
- github.io front end server, fastpages/github pages web application
- AWS backend server, flask/python web application
Frontend JavaScript
Look at HTML, actions and fetch commands/options. This is very similar to operations that can be performed in Postman.
- HTML code is gathering input and initiating creat_user() action
- The create_user() function obtains input from HTML, sets up options
- POST passes JSON data through body key in header
- On success, data updates Document Elements on screen
---
title: Database CRUD Operations
layout: default
description: An advanced example of do database operation asynchronously between JavaScript and Backend Database.
permalink: /data/database
image: /images/database.png
categories: [C4.7, C7.0, C8.1, C8.6]
tags: [javascript, fetch, get, post, put]
---
{% include nav_data.html %}
<p>Database API</p>
<table>
<thead>
<tr>
<th>User ID</th>
<th>Name</th>
<th>Posts</th>
<th>DOB</th>
<th>Age</th>
</tr>
</thead>
<tbody id="result">
<!-- javascript generated data -->
</tbody>
</table>
<p>Create API</p>
<form action="javascript:create_user()">
<p><label>
User ID:
<input type="text" name="uid" id="uid" required>
</label></p>
<p><label>
Name:
<input type="text" name="name" id="name" required>
</label></p>
<p><label>
Password:
<input type="password" name="password" id="password" required>
Verify Password:
<input type="password" name="passwordV" id="passwordV" required>
</label></p>
<p><label>
Birthday:
<input type="date" name="dob" id="dob">
</label></p>
<p>
<button>Create</button>
</p>
</form>
<script>
// prepare HTML result container for new output
const resultContainer = document.getElementById("result");
// prepare URL's to allow easy switch from deployment and localhost
//const url = "http://localhost:8086/api/users"
const url = "https://flask.nighthawkcodingsociety.com/api/users"
const create_fetch = url + '/create';
const read_fetch = url + '/';
// 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 create_user(){
//Validate Password (must be 6-20 characters in len)
//verifyPassword("click");
const body = {
uid: document.getElementById("uid").value,
name: document.getElementById("name").value,
password: document.getElementById("password").value,
dob: document.getElementById("dob").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) {
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 add_row(data) {
const tr = document.createElement("tr");
const uid = document.createElement("td");
const name = document.createElement("td");
const posts = document.createElement("td")
const dob = document.createElement("td");
const age = document.createElement("td");
// obtain data that is specific to the API
uid.innerHTML = data.uid;
name.innerHTML = data.name;
posts.innerHTML = data.posts.length;
dob.innerHTML = data.dob;
age.innerHTML = data.age;
// add HTML to container
tr.appendChild(uid);
tr.appendChild(name);
tr.appendChild(posts);
tr.appendChild(dob);
tr.appendChild(age);
resultContainer.appendChild(tr);
}
</script>
Backend Python API
CRUD and Security APIs, or web endpoints. All receive requests and respond with results. The results are JSON formatted data.
- THe class _Create contains a post method. This method performs validation on HTML inputs and on success add record to user table in the database
- The clase _Read contains a get method. This extract all users from user table.
import json
from flask import Blueprint, request, jsonify
from flask_restful import Api, Resource # used for REST API building
from datetime import datetime
from model.users import User
user_api = Blueprint('user_api', __name__,
url_prefix='/api/users')
# API docs https://flask-restful.readthedocs.io/en/latest/api.html
api = Api(user_api)
class UserAPI:
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) < 2:
return {'message': f'Name is missing, or is less than 2 characters'}, 400
# validate uid
uid = body.get('uid')
if uid is None or len(uid) < 2:
return {'message': f'User ID is missing, or is less than 2 characters'}, 400
# look for password and dob
password = body.get('password')
dob = body.get('dob')
''' #1: Key code block, setup USER OBJECT '''
uo = User(name=name,
uid=uid)
''' Additional garbage error checking '''
# set password if provided
if password is not None:
uo.set_password(password)
# convert to date type
if dob is not None:
try:
uo.dob = datetime.strptime(dob, '%Y-%m-%d').date()
except:
return {'message': f'Date of birth format error {dob}, must be mm-dd-yyyy'}, 400
''' #2: Key Code block to add user to database '''
# create user in database
user = uo.create()
# success returns json of user
if user:
return jsonify(user.read())
# failure returns error
return {'message': f'Processed {name}, either a format error or User ID {uid} is duplicate'}, 400
class _Read(Resource):
def get(self):
users = User.query.all() # read/extract all users from database
json_ready = [user.read() for user in users] # prepare output in json
return jsonify(json_ready) # jsonify creates Flask response object, more specific to APIs than json.dumps
class _Security(Resource):
def post(self):
''' Read data for json body '''
body = request.get_json()
''' Get Data '''
uid = body.get('uid')
if uid is None or len(uid) < 2:
return {'message': f'User ID is missing, or is less than 2 characters'}, 400
password = body.get('password')
''' Find user '''
user = User.query.filter_by(_uid=uid).first()
if user is None or not user.is_password(password):
return {'message': f"Invalid user id or password"}, 400
''' authenticated user '''
return jsonify(user.read())
# building RESTapi endpoint
api.add_resource(_Create, '/create')
api.add_resource(_Read, '/')
api.add_resource(_Security, '/authenticate')