express + mysql : clean code for complex queries - mysql

I am using express with mysql for my api.
This code works fine :
exports.addAuthor = function(req, res, next) { // POST /authors {{{
let sqlquery = '';
let a = checkAuthor(req.body);
if(!a.valid) {
var myerr = new Error(a.msg);
myerr.status = 400;
return next(myerr);
}
//
myPool.getConnection(function(err, connection) {
if (err) { return next(err); }
// check if exists first
sqlquery = 'SELECT COUNT(*) AS nb FROM authors';
sqlquery += ' WHERE name = "' + a.name + '" AND firstname = "' + a.firstname + '";';
connection.query(sqlquery, function(err, rows) {
if (err) {
connection.release();
return next(err);
} else {
if (rows[0].nb > 0) {
connection.release();
let myerr = new Error('This author already exists !');
myerr.status = 400;
return next(myerr);
} else {
sqlquery = 'INSERT INTO authors (name, firstname)';
sqlquery += ' VALUES ("' + a.name + '","' + a.firstname + '")';
connection.query(sqlquery, function(err, rows) {
connection.release();
if (err) { return next(err); }
if (rows.affectedRows) {
res.json({name: a.name, firstname: a.firstname, id: rows.insertId});
}
});
}
}
});
});
}; // }}}
First, I would like to reuse the code to get the id of the author, if it exists, so define one function for that.
Secondly I would like to write simpler and cleaner code as I imagine more complex code with several requests to the DB for future cases. (12 lines only to check if author exists !)
I know it has to deal with asynchronous nature of the Mysql queries.
I read many things but I am still no able to write proper code to acheive that.
Thanks to point me to right way to manage not so simple case.
JPM
I think I succeeded to make work some code that was not working yesterday !
I do not know yet if it is the best way however.
I took some piece of code somewhere but I do not remember where (probably here).
Here it is :
const execQuery = (sql, params) => new Promise((resolve, reject) => { // {{{
myPool.getConnection(function(err, connection) {
if (err) {
reject(err);
} else {
connection.query(sql, function(err, rows) {
connection.release();
if (err) {
reject(err);
} else {
resolve(rows);
}
})
}
});
}); // }}}
async function getIDAuthor(a) { // {{{
sqlquery = 'SELECT id FROM authors';
sqlquery += ' WHERE name = "' + a.name + '" AND firstname = "' + a.firstname + '"';
if(a.id) { // case update name of known author
sqlquery += ' AND id <> ' + a.id;
}
try {
rows = await execQuery(sqlquery);
if(rows.length>0) {
return rows[0].id;
} else {
return 0;
}
} catch (error) {
return -1;
}
} // }}}
exports.addAuthor = async function(req, res, next) { // POST /authors {{{
let sqlquery = '';
let a = checkAuthor(req.body); // {{{
if(!a.valid) {
var myerr = new Error(a.msg);
myerr.status = 400;
return next(myerr);
} // }}}
let ide = await getIDAuthor(a);
if(ide>0) {
let myerr = new Error('This author already exists !');
myerr.status = 400;
return next(myerr);
}
sqlquery = 'INSERT INTO authors (name, firstname)';
sqlquery += ' VALUES ("' + a.name + '","' + a.firstname + '")';
try {
rows = await execQuery(sqlquery);
if (rows.affectedRows) {
res.json({name: a.name, firstname: a.firstname, id: rows.insertId});
}
} catch (error) {
return next(error);
}
}; // }}}
I have not yet checked all possible errors.
Feel free to advise me or correct me if needed.

I took code and ideas from https://nemethgergely.com/error-handling-express-async-await/
So it looks now like this:
exports.addAuthor = asyncMiddleware(async function(req, res, next) { // POST /authors {{{
let sqlquery = '';
let rows;
//
let a = checkAuthor(req.body);
if(!a.valid) { throw boom.badRequest(a.msg); }
let ide = await getIDAuthor(a);
if(ide>0) { throw boom.badRequest('Cet auteur existe déjà !'); }
//
sqlquery = 'INSERT INTO authors (name, firstname)';
sqlquery += ' VALUES ("' + a.name + '","' + a.firstname + '")';
rows = await execQuery(sqlquery);
if (rows.affectedRows) {
res.json({name: a.name, firstname: a.firstname, id: rows.insertId});
}
}); // }}}
In fact this does not handle all error cases. Forget it. Still at start point. Grrrrrr.

Related

How to send multiple responses in node js

How to send multiple response in node js? In this code I want to send data and data1
function(req, res){
var classValue = req.query.classID;
var sql = "SELECT * FROM class WHERE `class_name` ='"+classValue+"'";
connection.query(sql, function(err, data){
if(data.length){
var sql1 = "SELECT * FROM book WHERE `class_id` ='"+data[0].class_id+"'";
connection.query(sql1, function(err, data1){
res.status(200).send(data1);
res.status(200).send(data);
});
}
});
}
Calling send will end the connection, so you can only call it once. You can send back a complex structure as JSON easily like this:
res.json( { data, data1 } );
Note that you have a path where data.length might be zero that still needs to be handled. You might consider something like this:
function(req, res, next){
var classValue = req.query.classID;
var sql = "SELECT * FROM class WHERE `class_name` ='" + classValue + "'";
connection.query(sql, function(err, data) {
if (err) return next(err); // <-- handle errors
if (data.length) {
var sql1 = "SELECT * FROM book WHERE `class_id` ='" + data[0].class_id + "'";
connection.query(sql1, function(err, data1) {
if (err) return next(err); // <-- handle errors
res.json( { data, data1 } );
});
}
else {
res.json( {data: null, data1: null} ); // or treat it as an error?
}
});
}
If it's an error condition that the first query gives no results, then you can treat it as an error. Note that I also added error handling using the next function (assuming you're using Express routing).

problem while using for loop in async function in node js es6

I have written this piece of code that retrieves a list of contacts and in response show a JSON everything works fine but the code doesn't show any output what should I have done? this is my code:
export const ContactListPost = (req, res) => {
//const userHeader = req.get('userid');
const contacts = req.body;
console.log(contacts);
let users = [];
const delay = contact => new Promise(function (resolve, reject) {
let sql = "select user.userid, user.fullname, user.phone,
photos.name as photo_link " +
"from user left outer join photos on photos.uploader_id = user.userid where user.phone = ?";
console.log("contact " + contact);
con.query(sql, [contact], function (err, result) {
if (err){
console.log(err);
reject(err);
} else{
if (result.length > 0) {
users.push(result);
console.log(result);
resolve(result);
}else{
console.log("empty number");
//reject(result);
}
}
})
});
(async function loop() {
for (let i = 0; i< contacts.length ; i++){
let result = await delay(contacts[i].number);
users.push(result);
console.log("result " + JSON.stringify(result));
console.log("i " + i);
}
console.log("users " + JSON.stringify(users));
res.json(JSON.stringify(users));
})();
};
this piece of code doesn't show
console.log("users " + JSON.stringify(users));
res.json(JSON.stringify(users));
thanks for advice
If the promise is rejected, the await expression throws the rejected value.
You need to handle rejected promises. If one of the calls to delay returns a rejected promise, the rejected value is thrown and the execution of the function will stop and control will be passed to the first catch block in the call stack.
await operator
throw statement

loopback custom query did not give response

my loopback custom query cannot give response after some time. below is my code.
var ds = tbljourney.dataSource;
ds.connector.query("select * from user where email='" + data.email + "'"
, function (err, user) {
user = user[0];
console.log("user", user);
if (err) {
console.log(err);
}
else if (user) {
}
});
some time it give a response but after 50 or 60 attemps its stuck
try to run your queries using the method below. Here's I'm connecting to an Oracle database. A lot of concat gets messy.
Model.remoteMethod(data,cb){
var app = require('../server');
var ds = app.datasources.newDatasource;
var sql = "SELECT * FROM TABLE WHERE SITE_ID=:param1 AND "+
"(DESTINATION=:param2 OR USERNAME=:param2 )";
var params = [param1, param2];
ds.connector.execute(sql, params, function(err, result){
if (err) console.error("error: " + err);
cb(err, result);
});
}

better way of selecting 1 to many?

I have an express.js based rest application. Please have a look on following code and suggest me what would be better way.
I want to select user and its associated images (1 user has many images).
function getUser (connection, req, res) {
var userId = req.params.id;
connection.query('SELECT * FROM user p'
+ ' WHERE p.id = ' + connection.escape(userId), function handleSql(err, rows) {
if (err){ logAndRespond(err,res); return; }
if (rows.length === 0){ res.send(204); return; }
var adId = rows[0].adId;
// load images
connection.query('SELECT id, url FROM image WHERE ad_id = ' + connection.escape(adId), function (err, imgRows) {
if (err){ logAndRespond(err,res); return; }
if (rows.length != 0){
rows[0].images = imgRows;
}
res.json({'user': rows});
connection.release();
});
});
}
You don't have to escape parameters by yourself
You don't release the connection if an error occurred
The problem now is I don't know what you want to do with selected rows. You are also checking the rows.length twice but if there weren't any records in the first query then the second one will not be executed.
function getUser(conn, req, res) {
conn.query("SELECT * FROM user p WHERE p.id = ?;", [req.params.id], function(err, rows) {
if (err) {
return logAndRespond(err, res);
}
if (!rows.length) {
return res.send(204);
}
conn.query("SELECT id, url FROM image WHERE ad_id = ?;", [rows[0].adId], function(err, imgRows) {
if (err) {
return logAndRespond(err, res);
}
if (rows.length) { // ???
rows[0].images = imgRows;
}
res.json({"user": rows});
conn.release();
});
});
}

Node.js mysql transaction

Can anyone provide an example of how I could achieve MySQL transactions in Node.js. I am trying to get my head around using the node-mysql driver and node-mysql-queue.
As far are I can tell, using node-mysql-queue greatly reduces the asynchronous nature of Node.js as new queries have to wait until existing ones have completed. To get around this, has anyone attempted to combine node-mysql-queue with node-mysql's connection-pooling capabilities. i.e starting a new mysql connection for each new http request, and starting transaction queues on individual connections?
Update
See the edit below for async/await syntax
I spent some time writing a generalized version of the transaction example given by node mysql, so I thought I would share it here. I am using Bluebird as my promise library, and used it to 'promisify' the connection object which simplified the asynchronous logic a lot.
const Promise = ('bluebird');
const mysql = ('mysql');
/**
* Run multiple queries on the database using a transaction. A list of SQL queries
* should be provided, along with a list of values to inject into the queries.
* #param {array} queries An array of mysql queries. These can contain `?`s
* which will be replaced with values in `queryValues`.
* #param {array} queryValues An array of arrays that is the same length as `queries`.
* Each array in `queryValues` should contain values to
* replace the `?`s in the corresponding query in `queries`.
* If a query has no `?`s, an empty array should be provided.
* #return {Promise} A Promise that is fulfilled with an array of the
* results of the passed in queries. The results in the
* returned array are at respective positions to the
* provided queries.
*/
function transaction(queries, queryValues) {
if (queries.length !== queryValues.length) {
return Promise.reject(
'Number of provided queries did not match the number of provided query values arrays'
)
}
const connection = mysql.createConnection(databaseConfigs);
Promise.promisifyAll(connection);
return connection.connectAsync()
.then(connection.beginTransactionAsync())
.then(() => {
const queryPromises = [];
queries.forEach((query, index) => {
queryPromises.push(connection.queryAsync(query, queryValues[index]));
});
return Promise.all(queryPromises);
})
.then(results => {
return connection.commitAsync()
.then(connection.endAsync())
.then(() => {
return results;
});
})
.catch(err => {
return connection.rollbackAsync()
.then(connection.endAsync())
.then(() => {
return Promise.reject(err);
});
});
}
If you wanted to use pooling as you suggested in the question, you could easily switch the createConnection line with myPool.getConnection(...), and switch the connection.end lines with connection.release().
Edit
I made another iteration of the code using the mysql2 library (same api as mysql but with promise support) and the new async/await operators. Here is that
const mysql = require('mysql2/promise')
/** See documentation from original answer */
async function transaction(queries, queryValues) {
if (queries.length !== queryValues.length) {
return Promise.reject(
'Number of provided queries did not match the number of provided query values arrays'
)
}
const connection = await mysql.createConnection(databaseConfigs)
try {
await connection.beginTransaction()
const queryPromises = []
queries.forEach((query, index) => {
queryPromises.push(connection.query(query, queryValues[index]))
})
const results = await Promise.all(queryPromises)
await connection.commit()
await connection.end()
return results
} catch (err) {
await connection.rollback()
await connection.end()
return Promise.reject(err)
}
}
The following transaction example was added to the documentation a month ago:
https://github.com/felixge/node-mysql#transactions
connection.beginTransaction(function(err) {
if (err) { throw err; }
connection.query('INSERT INTO posts SET title=?', title, function(err, result) {
if (err) {
connection.rollback(function() {
throw err;
});
}
var log = 'Post ' + result.insertId + ' added';
connection.query('INSERT INTO log SET data=?', log, function(err, result) {
if (err) {
connection.rollback(function() {
throw err;
});
}
connection.commit(function(err) {
if (err) {
connection.rollback(function() {
throw err;
});
}
console.log('success!');
});
});
});
});
I am using the following approach. There is an add function in my Model where I am performing database operations.
add : function (data, callback) {
//Begin transaction
connection.beginTransaction(function(err) {
if (err) {
throw err;
}
var user_query = "INSERT INTO `calldata`.`users` (`username`, `password`, `enabled`, `accountNonExpired`, `accountNonLocked`, `credentialsNonExpired`) VALUES ('" + data.mobile + "', '" + sha1(data.password) + "', '1', '1', '1', '1')";
connection.query(user_query, function(err, results) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
var accnt_dtls_query = "INSERT INTO `calldata`.`accnt_dtls` (`req_mob_nmbr`, `usr_nme`, `dvce_id`, `mngr_id`, `cmpny_id`, `actve_flg`, `crtd_on`, `usr`) VALUES (" + data.mobile + ", '" + data.name + "', '', " + data.managerId + ", " + data.companyId + ", 1, now(), '" + data.mobile+ "')";
connection.query(accnt_dtls_query, function(err, results) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
var user_role_query = "INSERT INTO `calldata`.`user_roles` (`username`, `ROLE`) VALUES ('" + data.mobile + "', '" + data.role + "')";
connection.query(user_role_query, function(err, result) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
//add an entry to manager table
var mngr_dtls_query = "INSERT INTO `calldata`.`mngr_dtls` (`mngr_nm`, `cmpny_id`, `crtd_on`, `usr_nm`, `eml_id`) VALUES ('" + data.name + "'," + data.companyId + " , now(), '" + data.mobile + "', '" + data.mobile + "')";
connection.query(mngr_dtls_query, function(err, result) {
if (err) {
return connection.rollback(function () {
throw err;
});
}
console.log('Changed ' + result.changedRows + ' results');
connection.commit(function (err) {
console.log('Commiting transaction.....');
if (err) {
return connection.rollback(function () {
throw err;
});
}
console.log('Transaction Complete.');
connection.end();
callback(null, result);
});
});
});
});
});
});
//transaction ends here
}
and calling from controller:
agentAccountModel.add(data, function(err, results) {
if(err)
{
res.status(500);
res.json({
"status": 500,
"message": err
});
}
res.status(200);
res.json({
"status": 200,
"message": "Saved successfully"
});
});
I have come up with a solution using a recursive function.
var sql = 'INSERT INTO logs SET data = ?';
// array of rows to insert
var rows = [[/*first row*/], [/*additional row*/]];
connection.beginTransaction(function (err) {
if (err) {
throw err;
}
var insertEachRow = function () {
var row = rows.shift();
if (! row) {
// Done, now commit
return noMoreRows();
}
connection.query(sql, row, function (err, result) {
if (err) {
connection.rollback(function () {
throw err;
});
}
insertEachRow();
});
};
var noMoreRows = function () {
connection.commit(function (err) {
if (err) {
connection.rollback(function () {
throw err;
});
}
console.log('success!');
});
};
insertEachRow();
});
I found one useful links which uses node js mysql pooling with transaction. Database Connection pooling is always useful. One can check this link
https://github.com/mysqljs/mysql
I created a wrapper ORM type thing for this specific purpose, hope it helps SQl-connecton - pool ORM type helper methods
You can use it with the mysql also, just use this function without any parameters /call back .
Make sure you have async function and use these instead
await con.rollback();
await con.beginTransaction();
await con.commit();
Life set.....