Refactoring the Server App to Remove Knex

In this post I replaced the Knex library I was using to construct queries with straight SQL queries using the normal mysql library using pooled connections and promises.

As I began making changes to my node-app to add the budget functionality I found myself having to convert the Knex code into a SQL query (and print to the console) to properly troubleshoot while I was developing. This seems kind of backwards as using Knex is meant to avoid needing to construct a query properly, and I figure brushing up my raw SQL skills would be much more applicable to future projects than a particular javascript implementation of it.

So refactor time, yay!

I moved all the database connection code back into the database.js file and made use of connection pooling to handle concurrent requests.

I added this to a new runQuery function which handles the connection and returns a promise so my queries could become “then-able”.

/* Converts the mysql pool query function into a Promise function */
function runQuery(query) {
    return new Promise(function (resolve, reject) {
        pool.getConnection((error, connection) => {
            if (error) reject(error);
            connection.query(query, (error, result) => {
                if (error) reject(error);
                else resolve(result);
            });
        });
    });
}

Then I could replace the functions that used the Knex query with one that calls the runQuery function instead, e.g:

destroyUserDatabase: () => {
    let q_dropTableUser = `DROP TABLE IF EXISTS user;`;
    return runQuery(q_dropTableUser)
        .then(() => '---destroyUserDatabase Drop Success')
        .catch((error) => new Error(error));
}

I also needed another helper class to convert JSON objects into a string of the format ("value1", "value2", ...), I added a few of these functions for the objects I needed but I’ll replace these with a better solution after this first refactor phase.

function user_JSONtoValueString(data) {
    let values = '';
    for (let i=0; i< data.length; i++) {
        if (i!=0) values += ",";
        values += "(\"";
        values += data[i].username + "\",\"";
        values += data[i].password + "\"";
        if (data[i].app_access) values += "," + data[i].app_access
        values += ")";
    }
    return values;
}

I could then replace the initialisation functions with straight SQL queries e.g. CREATE TABLE and insert values using INSERT INTO and the helper function.

initUserDatabase: (data) => {

    let q_createTableUser = `
    CREATE TABLE user (
        id INT NOT NULL AUTO_INCREMENT,
        username VARCHAR(255) NOT NULL UNIQUE,
        password VARCHAR(60) NOT NULL,
        app_access BOOLEAN NOT NULL DEFAULT 0,
        PRIMARY KEY (id)
    );`;
    let q_insertUser_full = '';
    if (data && data.user) q_insertUser_full = `INSERT INTO user (username, password, app_access) VALUES ` + user_JSONtoValueString(data.user);

    return runQuery(q_createTableUser)
        .then((result) => {
            console.log('---initUserDatabase Create Success');
            // Insert given data
            if (data && data.user) return runQuery(q_insertUser_full);
        })
        .then(() => '---initUserDatabase Insert Success')
        .catch((error) => new Error(error));

}

I think I’m violating a bunch of javascript style guides with this but this is much more straight-forward and easily troubleshooted (troubleshot?), I consider it a great success!

initBudgetDatabase: () => {
    let q_createTableAccount = `
    CREATE TABLE account (
        id INT NOT NULL AUTO_INCREMENT,
        name VARCHAR(255) NOT NULL,
        active BOOLEAN NOT NULL DEFAULT 1,
        user_id INT NOT NULL,
        PRIMARY KEY (id),
        FOREIGN KEY (user_id) REFERENCES user(id)
    );`
    let q_createTableCategory = `
    CREATE TABLE category (
        id INT NOT NULL AUTO_INCREMENT,
        name VARCHAR(255) NOT NULL,
        user_id INT NOT NULL,
        PRIMARY KEY (id),
        FOREIGN KEY (user_id) REFERENCES user(id)
    );`
    let q_createTableTransaction = `
    CREATE TABLE transaction (
        id INT NOT NULL AUTO_INCREMENT,
        date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        notes VARCHAR(255),
        amount DECIMAL(19,2) NOT NULL,
        cleared BOOLEAN NOT NULL DEFAULT 0,
        user_id INT NOT NULL,
        payee_id INT NOT NULL,
        category_id INT NOT NULL,
        account_id INT NOT NULL,
        PRIMARY KEY (id),
        FOREIGN KEY (user_id) REFERENCES user(id),
        FOREIGN KEY (payee_id) REFERENCES account(id),
        FOREIGN KEY (category_id) REFERENCES category(id),
        FOREIGN KEY (account_id) REFERENCES account(id)
    );`
    let q_createTableBudget = `
    CREATE TABLE budget (
        id INT NOT NULL AUTO_INCREMENT,
        amount DECIMAL(19,2) NOT NULL DEFAULT 0,
        date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        category_id INT NOT NULL,
        PRIMARY KEY (id),
        FOREIGN KEY (category_id) REFERENCES category(id)
    );`
    let createQuery = 
        q_createTableAccount +
        q_createTableCategory + 
        q_createTableTransaction +
        q_createTableBudget;
    return runQuery(createQuery)
        .then(() => '---initBudgetDatabase Create Success')
        .catch((error) => new Error(error));
}

Then I could simplfy my init scripts to chain the “then”s to end up with this masterpiece:

cleanInit: (data) => {
    db.destroySensorDatabase()
        .then((result) => {
            console.log(result);
            return db.destroyBudgetDatabase()
        })
        .then((result) => {
            console.log(result);
            return db.destroyUserDatabase();
        })
        .then ((result) => {
            console.log(result);
            return db.initUserDatabase(data);
        })
        .then((result) => {
            console.log(result);
            return db.initBudgetDatabase()
        })
        .then((result) => {
            console.log(result);
            return db.initSensorDatabase(data);
        })
        .then((result) => {
            console.log(result);
            process.exit();
        })
        .catch((error) => {
            console.log(error);
            process.exit(1);
        });

Beautiful, now I can get on with the rest of these projects…


About Me

Engineer, maker, do-er...
I basically just like to make things.

Archives