Fetch, JSON, Vanilla-JS and PHP

I’m setting up a registration/login in system for my online trivia game and have come up with a dynamic way to check for username duplicates in a MySQL Database Table in real time.

First the Vanilla-JS script:

'use strict';

const d = document;
const sendUrl = 'checkName.php'; // Name of php file:
const unavailable = d.querySelector('.unavailable');
var username = d.querySelector('#username');
var checkUsername = {};

/* Success function utilizing FETCH */
const checkUISuccess = function (status) {
    /*
     * Make <span> HTML tag visible to highlight message
     * in red.
     */
    if (status) {
        unavailable.style.display = "inline-block";
    } else {
        unavailable.style.display = "none";
    }
};

/* If Database Table fails to update data in mysql table */
const checkUIError = function (error) {
    console.log("Database Table did not load", error);
};

/*
 * Grab the status if there is an error.
 */
const handleSaveErrors = function (response) {
    if (!response.ok) {
        throw (response.status + ' : ' + response.statusText);
    }
    return response.json();
};

/*
 *  Fetch ($.get in jQuery) that basic is a simplified newer Ajax
 *  protocol (function?). 
 */
const checkRequest = (sendUrl, succeed, fail) => {
    fetch(sendUrl, {
        method: 'POST', // or 'PUT'
        body: JSON.stringify(checkUsername)

    })
            .then((response) => handleSaveErrors(response))
            .then((data) => succeed(data))
            .catch((error) => fail(error));
};

/*
 *  Grab the keystrokes
 */
const checkForDuplicate = (e) => {
    e.preventDefault();
    checkUsername.username = username.value; // Put the keystrokes in object var:    
    
    /* 
     *  Call the checkRequest Function using sendUrl variable as the name
     *  of the php file that will be used to check against the database table.
     */ 
    
    checkRequest(sendUrl, checkUISuccess, checkUIError); 
};

/*
 * Add an Event Listener to check for username already in 
 * database table. When the person types every keyup stroke
 * is checked against the database table. 
 */

username.addEventListener('keyup', checkForDuplicate, false);

The HTML

    <form class="registerForm" action="" method="post" autocomplete="on">

        <h1><?php echo (isset($message)) ? $message : 'Register'; ?></h1>
        <p><?php echo (isset($errPassword)) ? $errPassword : "Please fill in this form to create an account."; ?></p>
        <hr>

        <label for="username"><b>Username <span class="unavailable"> - Not Available, please choose a different one.</span></b></label>
        <input id="username" type="text" placeholder="<?php echo (isset($statusUsername) && $statusUsername) ? "Username is not available, please re-enter!" : "Enter Username"; ?>" name="data[username]" value="" autofocus required>

        <label for="email"><b>Email</b></label>
        <input type="text" placeholder="Enter Email" name="data[email]" value="<?php echo (isset($data['email'])) ? $data['email'] : null; ?>" required>

        <label for="psw"><b>Password</b></label>
        <input type="password" placeholder="Enter Password" name="data[password]" required>

        <label for="psw-repeat"><b>Repeat Password</b></label>
        <input type="password" placeholder="Repeat Password" name="data[repeatPassword]" required>
        <hr>

        <p>By creating an account you agree to our <a href="#">Terms & Privacy</a>.</p>
        <input type="submit" name="submit" value="enter" class="registerbtn">


        <div class="signin">
            <p>Already have an account? <a href="index.php">Sign in</a>.</p>
        </div>
    </form>

the checkName.php

<?php
require_once 'assets/config/config.php';
require_once "vendor/autoload.php";
require_once 'loginFunctions.php';

use Library\Users;
use Library\Database as DB;

$db = DB::getInstance();
$pdo = $db->getConnection();

/*
 * The below must be used in order for the json to be decoded properly.
 */
$data = json_decode(file_get_contents('php://input'), true);

/*
 * Check to see if username already exists
 * in the MySQL Database Table (members).
 */
function duplicateUsername($username, $pdo) {


        $query = "SELECT 1 FROM members WHERE username = :username";
        $stmt = $pdo->prepare($query);
        $stmt->bindParam(':username', $username);
        $stmt->execute();
        $row = $stmt->fetch();
        if ($row) {
            return true; // userName is in database table
        } else {
            return false; // Username isn't in database table
        }

}

$username = trim($data['username']);

$status = duplicateUsername($username, $pdo);

output($status);


/*
 * After encoding data to JSON send back to javascript using
 * these functions.
 */

function errorOutput($output, $code = 500) {
    http_response_code($code);
    echo json_encode($output);
}

function output($output) {
    http_response_code(200);
    echo json_encode($output);
}

and a little bit of the CSS

.unavailable { display: none; color: #cf0004; font-style: italic; }

.registerForm {
    display: block;
    width: 100%;
    max-width: 600px;
    height: auto;
    background-color: #fff;
    padding: 16px;
    margin: 20px auto;
    /* Full-width input fields */
    input[type=text], input[type=password] {
        width: 100%;
        padding: 15px;
        margin: 5px 0 22px 0;
        display: inline-block;
        border: none;
        background: #f1f1f1;
    }
    input[type=text]:focus, input[type=password]:focus {
        background-color: #ddd;
        outline: none;
    }

    /* Overwrite default styles of hr */
    hr {
        border: 1px solid #f1f1f1;
        margin-bottom: 25px;
    }

    /* Set a style for the submit/register button */
    .registerbtn {
        background-color: #4CAF50;
        color: white;
        padding: 16px 20px;
        margin: 8px 0;
        border: none;
        cursor: pointer;
        text-transform: capitalize;
        font-size: 1.2em;
        width: 100%;
        opacity: 0.9;
        &:hover {
            opacity:1;
        }
    }

Sorry, it’s written in Sass, but I think it would be easy to decipher. I hope this will give someone help in doing something like this and/or how to use Fetch. Just a note Fetch is supported by all browsers though it’s mostly older browsers. Though if you are concerned about that you can probably find a polyfill for the script, but as for me I don’t worry about that too much.

You dont need a function to check for duplicates. Just set a unique index on the column, attempt the insert and capture the duplicate error if any.

I like the real time aspect of checking for duplicates, I know I could do it that way. Would it do it in real time? This is what I mean -> https://chalkboardquiz.com/register.php

If you type in Strider it will flag for a duplicate. Basically, I was goofing around as I’m bored with this stay-at-home order in Michigan. Though I will implement that on the php side if someone disables Javascript. Though they still couldn’t play the game. LOL :smile:

Edit - I’m still going to do server-side validation as I was just joking that the quiz totally relies on javascript.

This issue is essentially client-side validation, supported by data that’s on the server. You CAN do client side validation, as a nicety for the visitor. You MUST do server-side validation, which would include enforcement of unique constraints, since the server is last in line to deal with any data submitted to it, which can be anything, cannot be trusted, and might not even be coming from your client-side code.

Your database table must have a unique index and your server-side code must have error handling, so that when the data is finally submitted and inserted, duplicates will be prevented and you can report back to the visitor that between the time when they filled in the form and when they submitted it, that the username (or whatever meaning data) was already taken.

While the frequency of this condition occurring will be low, what will your current application code do if it does happen and you end up with duplicate values stored in the database table?

In your existing php code, the try/catch logic should be removed. Any error that could occur for a SELECT query cannot be fixed by the visitor and there’s no point in telling the visitor anything about such an error. The type of information output by unconditionally echoing the ->getMessage() value would only be of use to a hacker, who is intentionally trying to trigger errors (think about someone submitting a several mega-byte long username value to trigger a maximum packet length error.)

In your INSERT query code, you would have a try/catch block, where you would test if the sql error number or sql state value is for something that your code is designed to deal with, then setup an appropriate error message for the visitor telling them what was wrong with the data that was submitted.

I have error reporting turned off on the remote server side, plus I do check server validation. I just didn’t make myself clear which I’m unfortunately good at.:unamused: I was just using old code and failed to realize that the I had the try-catch block as I was just busy trying to get the json portion working. I don’t like typing and this is something that I’m bad at not checking the old code. I’m going take out that out. Nice catch, I basically have stop using try-catch blocks as I’m a small operation and that just slipped through the cracks. I’m going to do the unique index on the server side and get rid of duplication function for that side of validation. Though I think I’m going to do that for the email address as I was just using duplicate more for gameplay than security. While I still like people to have a unique username, it wouldn’t hurt if there was a duplicate username. It will just look a little funny on the high scores listing. I still have to implement a few things on both the server side and the client side when it comes to validation.

That brings up a question can you have more than one unique column field in MySQL?

You can have as many unique constraints as you want. Put a unique constraint on the email and the username at minimum

Sponsor our Newsletter | Privacy Policy | Terms of Service