18

I'm working on creating a user registration system for a website that I am working on but I am running into a few issues.

I'm trying to stay away from having to nest callbacks because it gets kind of messy, What I need help with is finding if there is a way to create synchronous queries with node-mysql

Here's what I'm trying to achieve.

connection.query("select 1 as email from users where email = " + connection.escape(email), function(err, rows, fields) {
    if(err) {
        var error = {
            error_message: err.code,
            error_number: err.errno
        };

        return res.send(error);
    }

    if(rows.length > 0) {
        var error = {
            message: 'Email Address is Taken',
            code: 2
        };
        return res.send(error);
    }
});

connection.query("insert into users (email, password) values ("+connection.escape(email)+", "+connection.escape(hash)+")", function(err, rows, fields) {
            if(err) {
                var error = {
                    error_message: err.code,
                    error_number: err.errno
                };

                return res.send(error);
            }
        });

My goal is to have the first query run and if that returns a row then to not execute the second query but if the first query returns 0 rows then continue and run the second query.

I know I can nest the second query inside the first query and put if in an else but that's what I don't want to do because while I have those two queries I also have it set u to use bcrypt to encrypt the password which would have to be nested as well.

Is there a way to write it so that I don't need to nest the two queries or is nesting them going to be my only option?

Alex Beebe
  • 327
  • 1
  • 3
  • 7
  • You have to nest them. – Yuri Zarubin Sep 29 '15 at 17:25
  • Don't need to use nesting. You can use async waterfall, and pass a true/false variable between steps. If you can't want to run the second query, just pass false from the first. – CargoMeister Sep 29 '15 at 17:29
  • 9
    If you're going to develop in node.js, you will HAVE to learn how to write good async code as that is a main architectural element of node.js and if you are building a server, you have to use async operations to maintain any sense of server responsiveness and scalability. Might as well start now. I'd suggest learning how to use promises so you can chain sequential operations rather than nest and it makes error handling a ton easier. – jfriend00 Sep 29 '15 at 17:30
  • Here's another interesting method, using recursion. http://seanvbaker.com/using-recursion-to-tame-callback-hell/ – CargoMeister Sep 29 '15 at 17:31
  • I hope you fixed your queries to prevent possible SQL injections too. See https://stackoverflow.com/questions/15778572/preventing-sql-injection-in-node-js – Alexis Wilke Nov 24 '18 at 23:10

7 Answers7

24

You could simply use a module for node that provide synchronous functions. Here you'll find a module that provide sync/async functions to deal with mysql.

https://github.com/Will-I4M/node-mysql-libmysqlclient

Here is how you could use it in order to execute a synchronous query :

var config = require("./config.json") ;
var mysql = require('mysql-libmysqlclient') ;
var client = mysql.createConnectionSync(config.host, config.user, config.password, config.database) ;

var query = "SELECT * FROM Users ;" ;
var handle = client.querySync(query) ;
var results = handle.fetchAllSync() ;

console.log(JSON.stringify(results)) ; 
will.I4M
  • 343
  • 2
  • 6
  • Thanks for this! I actually needed a synchronous lib to chain into a bunch of reducers. – just_wes May 24 '16 at 12:29
  • As a side note, a reducer is not a real reducer (looses among other things its ability to be tested easily) if it is not a pure function that returns a value consistently based on its arguments. No side effects, no getting data separately from an external source. – Ben Dadsetan Apr 03 '17 at 10:25
  • Not sure to understand what you mean. This is "truly" synchonous. I made a lot of test on this lib, as I wrote the last version, for some reasons (difficult to explain here in few words) this is 3x faster than the pure node.js node-mysql asynchonous client. – will.I4M Apr 04 '17 at 11:22
  • @will.I4M where I can find examples that showcase different methods and their behaviour ? – Sohan Jan 28 '20 at 12:14
  • @Sohan You can find one example here : https://github.com/Will-I4M/node-mysql-libmysqlclient/blob/master/doc/examples/examples.js But I didn't updated this lib since 2016, you should have a look on the main repository. – will.I4M Jan 29 '20 at 14:28
  • @will.I4M Thanks, I will have a look. I am looking for similar sync behavior for mysql queries – Sohan Jan 30 '20 at 05:50
15

As jfriend00 said above, if you're going to develop in node.js, then you MUST become comfortable with writing async code.

"chained promises" is probably your best bet:

ADDENDUM:

This tutorial illustrates promise chaining with node.js SQL queries. It also discusses how you can use Q and/or Step to simplify your code:

paulsm4
  • 114,292
  • 17
  • 138
  • 190
  • Thanks for the examples. So for what I'm trying to do if I were to use promises how would I go about it so that I don't have to nest the queries? From that last example I started to implement promises into my code and this is what I have so far `Q.ninvoke(db, "query", 'SELECT 1 as email_exists FROM users WHERE email = ' + db.escape(email)) .then(function(rows) { if(rows[0].length > 0) { error = { error_message: 'Email Address is Taken', error_code: 8 }; res.end(error); } })` but won't I still need to nest the second query in the else statement that is in the then – Alex Beebe Sep 29 '15 at 19:50
  • Or can I have the second query in another .then(function() {}); after thr first one? – Alex Beebe Sep 29 '15 at 19:52
  • 3
    Thit is definitely not the answer of the question "Node.js Synchronous queries with MySQL" – Glastis Jan 18 '21 at 16:30
6

People talk about chained promises here, but give no example code. Here's what we did in a training session today to run a sequence of SQL statements synchronously using promises (credits to trainer and trainees), no additional libraries required:

let mysql = require("mysql");

let conn = mysql.createConnection({host: "localhost", user: "app",
  password: "*******", database: "people"});

//returns a promise that resolves to a result set on success
function execSql(statement, values) {
  let p = new Promise(function (res, rej) {
    conn.query(statement, values, function (err, result) {
      if (err) rej(err);
      else res(result);
    });
  });
  return p;
}

function insertUserAndFriend(user, friend) {
  execSql("INSERT INTO usr (nam) VALUES (?);",[user])
  .then(function (result) {
    console.log("Inserted " + user);
    return execSql("SELECT id, nam from usr where nam = ?;", [user]);
  })
  .then((result) => {
    let { id, nam } = result[0];
    console.log("Result: " + id + " " + nam);
    return execSql("INSERT INTO friend (usr,nam) VALUES (?,?);",
                      [id, friend]);
  })
  .then((result) => {
    console.log("Inserted " + friend);
  })
  .catch((err) => {
    console.log("Error: " + err);
  })
  .finally(function (res) {
    conn.end();
  });
}

conn.connect(function (err) {
  if (err) throw err;
  insertUserAndFriend("Bonnie", "Clyde");
});

For reference, here is the create.sql of the toy database:

DROP TABLE IF EXISTS friend;
DROP TABLE IF EXISTS usr;

CREATE TABLE usr (
    id   INT unsigned NOT NULL AUTO_INCREMENT,
    nam  VARCHAR(50) UNIQUE NOT NULL,
    PRIMARY KEY (id)
);


CREATE TABLE friend (
    usr INT unsigned NOT NULL,
    FOREIGN KEY (usr) REFERENCES usr (id),
    nam  VARCHAR(50) UNIQUE NOT NULL
);
NotARobot
  • 63
  • 1
  • 4
  • How would you extend your use case to support transactions? (a set of commands which should be executed at once or be reverted back in case of failure on any stage) – Gleichmut Jan 26 '23 at 12:39
5

There could be conditions when you need sync queries (or at least for readability or simplicity). I do not agree with that everything have to be done in the async way at node.js.

I have tested a lot of available solutions and ended up with the "sync-mysql" module (https://github.com/ForbesLindesay/sync-mysql).

Easy to install and use, but not that good in performance (especially if you have to do a lot of sub-queries).

Zoltán Hajdú
  • 485
  • 7
  • 8
1

For most things I code in node.js, I like asynchronous code. However, I completely understand that asynchronous code is extremely and dangerously incompatible with the need to write and maintain business logic. I've used a variety of alternative methods. The modules to make things synchronous still leave you with data scoping issues that complicate things. Promises worked best for me. Using that approach, I found myself practically writing an interpreter for a new language on top of JavaScript. I may seem absurd but the most practical and safest method for me ended up being to use the shelljs module and the mysql shell client. It's not great execution performance but it makes for much better developer performance and keeps business logic clear and orderly, as is crucial for business logic. Here's snippet of code to give an example of some of what I created:

var shell = require('shelljs');

module.exports = {
    user: '',
    password: '',

    runSql: function (sql) {
        var command = "echo '" + sql.replace(/'/g, "'\\''") + "' | mysql -u" + this.user.replace(/'/g, "'\\''") + " -p'" + this.password.replace(/'/g, "'\\''") + "'";
        var raw = shell.exec(command, {silent: true}).stdout;
        //console.log( 'BASH -> MySQL YIELD: "' + raw + '"' );
        if (raw.substr(0, 5) === 'ERROR') {
            console.log('ERROR Resulting from: ' + sql + '\n' + raw);
            return [];
        }
        var rows = raw.split('\n');
        var names = [];
        for (var r = 0; r < rows.length; r += 1) {
            columns = rows[r].split('\t');

            // Capture column names
            if (r === 0) {
                names = columns;
                continue;
            }

            // Reformat row into named valued
            var fields = {};
            for (var c = 0; c < columns.length; c += 1) {
                fields[names[c]] = columns[c];
            }
            rows[r] = fields;
        }

        // Eliminate extraneous first and last rows
        rows.splice(0, 1);
        rows.splice(rows.length - 1, 1);

        return rows;
    },

}
nvioli
  • 4,137
  • 3
  • 22
  • 38
  • Later, I added something to check if an error resulted and report that... And, I wrote another method that uses this method to get table design information, via MySQL's "describe tablename" command. It works great. After so much pain trying to more technically proper approaches advocated by people who don't understand the need, I can live with myself doing it this way. ultimately, somebody needs to write a MySQL module that offers both synchronous and asynchronous methods. Personally, I really wish JavaScript had a "do together { .. }" and "do in order { .. }" statements. – user1018645 Jul 29 '17 at 03:53
1

Symplest solution I could find is the sync-sql module. Install the required modules

npm install sync-sql
npm install sync-mysql 

Sample index.js

const Mysql = require('sync-mysql') 


const connection = new Mysql({ 
    host:'localhost', 
    user:'root', 
    password:'password', 
    database:'demo'
}) 
  
var result = connection.query('SELECT NOW()') 
console.log(result) 

https://www.geeksforgeeks.org/how-to-run-synchronous-queries-using-sync-sql-module-in-node-js/

Akhil
  • 2,602
  • 23
  • 36
0

I know I am late to this party but I feel I can help people like me that needed a way to use MySQL in a synchronous way.

Answer is here.

Oh and I had to add a pool.end(); after my query code to close the connection and stop the infinite wait loop. See here.

  • Your answer gave me an idea to my "infinite wait loop". I'm not using pooling yet, but I needed to add "await connection.destroy(); " and now NodeJS from the command prompt ends normally! – NealWalters Aug 22 '19 at 19:11
  • Reading the answer requires creating a user account. – Antti A Oct 31 '20 at 12:35