How to merge various MySQL queries results in a single JSON object - mysql

I'm trying to write a REST API set in NodeJS to retrieve data from my MySQL database.
Here the code:
var express = require('express');
var mysql = require('mysql');
var app = express();
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'root',
database : 'apitest',
port : '3306',
multipleStatements: true
});
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
app.get('/users', function(req, res) {
connection.query('SELECT name, surname, address FROM users', function(err, results) {
if (err) throw err;
if (results) {
res.status(200).send(results);
};
});
});
app.get('/users/:userId', function (req, res) {
var userId = req.params.userId;
connection.query('SELECT * FROM users WHERE idUser = ?', [userId], function(err, result) {
if (err) throw err;
if (result) {
res.status(200).send(result);
};
});
});
app.get('/users/:userId/photos', function (req, res) {
var userId = req.params.userId;
connection.query('SELECT date, file, tags FROM photos WHERE idUser = ?', [userId], function(err, results) {
if (err) throw err;
if (results) {
res.status(200).send(JSON.stringify(results));
}
});
});
var server = app.listen(8080, function () {
var host = server.address().address;
var port = server.address().port;
console.log('Listening on http://%s:%s', host, port);
});
Everything goes well if I use the single REST call as written above.
The problem is that I want it to work differently, so when i call /users/:userId i want to retrieve user data and the relative photos in a single, well structured, JSON response.
Eg:
{
"name" : "John",
"surname" : "Doe",
"photos" : [
{
"date" : "2015-04-19T22:00:00.000Z",
"file" : "photo1.jpg",
"tags" : "holidays, 2015"
},
{
"date" : "2015-04-19T22:00:00.000Z",
"file" : "photo2.jpg",
"tags" : "holidays, 2015, nassau"
}
]
}
I've find a workaround by modifying the /users/:userId call as mentioned above:
app.get('/users/:userId', function (req, res) {
var userId = req.params.userId;
connection.query('SELECT * FROM users WHERE idUser = ?', [userId], function(err, results1) {
if (err) throw err;
if (results1) {
connection.query('SELECT date, file, tags FROM photos WHERE idUser = ?', [userId], function(err, results2) {
if (err) throw err;
if (results2) {
results1[0].photos = results2;
res.status(200).end(JSON.stringify(results1[0]));
}
});
}
});
});
Everything seems to go well but I think is not the right way because if i want to add more information from other tables in my object i would have to nest more and more functions...
Any suggestion?
Thanks in advance.

Check out Async, or any one of the popular promise libaries(when.js, Q.js, Bluebird).
In Async, it might look something like this.
var async = require('async');
app.get('/users/:userId', function (req, res) {
var userId = req.params.userId;
async.parallel({
user: function(callback){
connection.query('SELECT * FROM users WHERE idUser = ?', [userId], callback)
},
photos: function(callback){
connection.query('SELECT date, file, tags FROM photos WHERE idUser = ?', [userId], callback)
}
},
// Final callback, with all the results
function(err, results){
//results now has {user: ..., photos: ...}
var user = results.user;
user.photos = results.photos;
res.status(200).end(JSON.stringify(user));
});
});
Adding another call is as simple as adding another function inside parallel (or whatever it may be). The code is pretty similar for the promise libraries so I'll leave that as an exercise to you!
Let me know that this helped.

Related

Using Promises in SQL query?

I have an API call that requires some information to be fetched from MySQL database in order to fulfill the request. The problem is, NodeJS won't wait for query response and I have tried to solve this with Promises. Here is my code:
app.post('/placeOrder', async function (req, res) {
console.log(req.body.orderInfo);
var username = decryptAccessToken(req.body.AuthToken);
console.log(username);
var firstQuery = await fetchCustomerInfo(username, req.body.orderInfo);
con.query("INSERT INTO Orders SET ?", firstQuery, function (err, response) {
if (!err) {
console.log("Order successfully placed");
res.send("Order successfully placed");
} else {
console.log(err);
}
});
});
async function fetchCustomerInfo(username, orderInfo) {
return new Promise((resolve, reject) => {
con.query("SELECT FirstName, LastName, Address, Email, Phone FROM Customers WHERE Email=?", username, function (err, response) {
var createOrder = {
FirstName: response.FirstName,
LastName: response.LastName,
Address: response.Address,
Email: response.Email,
Phone: response.Phone,
ProductInfoJSON: orderInfo
}
console.log(createOrder);
resolve(createOrder);
})
})
}
This fetchCustomerInfo function will just return Promise object and that will trigger SQL syntax error, because that is not expected database input. What I'm doing wrong? Any advise is highly appreciated.
Update: SQL error has been solved with Murat's answer, but database query still returns undefined. I have made sure that the query works in console when used manually.
Try this:
app.post('/placeOrder', async function (req, res) {
var username = decryptAccessToken(req.body.AuthToken);
console.log(username);
var firstQuery = await fetchCustomerInfo(username, req.body.orderInfo);
con.query("INSERT INTO Orders SET ?", firstQuery, function (err, response) {
if (!err) {
console.log("Order successfully placed");
res.status(200).send("Order successfully placed");
} else {
console.log(err);
res.status(500).send("Error!");
}
});
});
function fetchCustomerInfo(username, orderInfo) {
return new Promise((resolve, reject) => {
con.query("SELECT FirstName, LastName, Address, Email, Phone FROM Customers WHERE Email=?", username, function (err, response) {
var createOrder = {
FirstName: response.FirstName,
LastName: response.LastName,
Address: response.Address,
Email: response.Email,
Phone: response.Phone,
ProductInfoJSON: orderInfo
}
console.log(createOrder);
resolve(createOrder);
})
})
}
Edit:
Since I have not had much experience with the MySql library, I did not check the accuracy of the function while writing the answer. However, the way you use con.query() is wrong. If you look at W3 Schools, this is the right way to use it.
var mysql = require('mysql');
var con = mysql.createConnection({
host: "localhost",
user: "yourusername",
password: "yourpassword",
database: "mydb"
});
con.connect(function(err) {
if (err) throw err;
con.query("SELECT * FROM customers", function (err, result, fields) {
if (err) throw err;
console.log(result);
});
});

Node.js, Mysql TypeError: Cannot read property 'apikey' of undefined

I am working on a basic auth middleware for a API it uses Node.js Mysql but if someone puts a incorrect key in auth header and sends the request the entire API crashes heres my code the issue is with the callback but I don't know how to fix that.
var express = require('express');
var app = express();
app.get('/', (request, response) => {
response.sendStatus(200);
});
let listener = app.listen(3000, () => {
console.log('Your app is currently listening on port: ' + listener.address().port);
});
var mysql = require('mysql');
var connection = mysql.createConnection({
host : '127.0.0.1',
user : 'root',
database : 'systemdata'
});
connection.connect();
function systemAuth(apikey, callback)
{
connection.query('SELECT apikey FROM systemdata.systemkeys WHERE apikey = ?', [apikey], function(err, result)
{
if (err)
callback(err,null);
else
callback(null,result[0].apikey);
});
}
var auth = function (req, res, next) {
systemAuth(req.headers.apikey, function(err,data){
if (err) {
console.log("ERROR : ",err);
} else {
console.log("result from db is : ",data);
}
if(data == req.headers.apikey) {
next()
}else{
res.status(401).send({"error": "Missing or Invalid API-Key", "apikey": req.headers.apikey, "valid": "false"})
}
})
}
app.use(auth)
You will also have to check whether your result actually contains any rows.
A query not returning any rows is not an error, so err won't be set, if result is an empty array. And accessing an element by an index which does not exist leads to undefined, thus the error you are seeing.
function systemAuth(apikey, callback)
{
connection.query('SELECT apikey FROM systemdata.systemkeys WHERE apikey = ?', [apikey], function(err, result)
{
if (err) // some error with the query
callback(err,null);
else if (!result || result.length == 0) // no matching rows found
callback(new Error("invalid apikey"), null);
else // a matching row is found
callback(null,result[0].apikey);
});
}

express.js with MySQL

I just started learning node.js...
Here is an example of my code. In this example everything works.
But, I have a question. How to make several SQL queries and send results to template?
At the moment I can only do this for one query...
Thanks.
//connection database
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'password',
database : 'test'
});
connection.connect(function (err){
if (err) throw err;
console.log('Database connected . . . \n\n');
});
router.get('/', function(req, res, next) {
var sql = 'SELECT * FROM `test`';
connection.query(sql, function(err, rows, field){
if (err) throw err;
res.render('index', {
data: rows
})
});
});
Here is an answer following my comment since you mentioned you couldn't figure it out on your own.
First snippet uses promises, a quick helper function, but no external library. Second snippet uses the external async.js library and is a bit more callback-heavy. Both of them tackle the problem assuming we want the queries to be executed in parallel.
With promises
router.get('/', async function(req, res, next) {
var queries = ['SELECT * FROM `test`',
'SELECT * FROM `test2`',
'SELECT * FROM `test3`'];
var allResults = [];
/*transform our `query` array into an array of promises, then
await the parallel resolution of all the promises*/
var allQueryRows = await Promise.all(queries.map(query => promiseQuery(query)));
/*'allQueryRows' is an array of rows, so we push each of those
into our results*/
allQueryRows.forEach(function(rows){
allResults.push(...rows);
});
res.render('index', {
data: allResults
})
});
function promiseQuery(sqlQuery){
return new Promise((resolve, reject) => {
connection.query(sqlQuery, function(err, rows, field){
if(err)
return reject(err);
resolve(rows);
})
})
}
With callbacks and async.js
const async = require('async');
router.get('/', function(req, res, next) {
var queries = ['SELECT * FROM `test`',
'SELECT * FROM `test2`',
'SELECT * FROM `test3`'];
var allResults = [];
async.each(queries, function(sqlQuery, callback){
connection.query(sqlQuery, function(err, rows, field){
if(err)
throw err;
allResults.push(...rows);
callback();
});
}, function(){
res.render('index', {
data: allResults
});
});
});

Node.js - MySQL API, multi GET functions

I'm new in making API. I use Node.js and MySQL.
The fact is I have two GET function to get all users and one to get user by ID.
Both function are working when they are alone implemented. If both of them are implemented the function to get all user try to enter in the function to get user by ID so the API crash.
So here is my model users.js
var connection = require("../connection");
function Users()
{
//GET ALL USERS
this.get = function(res)
{
console.log('Request without id');
connection.acquire(function(err, con)
{
con.query('SELECT * FROM users', function(err, result)
{
con.release();
if (err)
res.send({status: 1, message: 'Failed to get users'})
else
res.send(result);
});
});
}
//GET USER BY ID
this.get = function(id, res)
{
console.log('Request with ID');
connection.acquire(function(err, con)
{
if (id != null)
{
con.query('SELECT * FROM users WHERE id = ?', id, function(err, result)
{
con.release();
if (err)
res.send({status: 1, message: 'Failed to find user: ' + id});
else if (result == "")
res.send({status: 1, message: 'Failed to find user: ' + id});
else
res.send(result);
});
}
});
}
And here is the routes.js
var users = require('./models/users');
module.exports = {
configure: function(app) {
app.get('/users/', function(req, res) {
users.get(res);
});
app.get('/users/:id/', function(req, res) {
users.get(req.params.id, res);
});
Do you have any idea why ?
Thanks for help :)
You can't have two functions with the same name in the same scope.
You have to rename your functions
/**
* Get all users
*/
this.get = function(res) {...}
/**
* Get user by id
*/
this.getById = function(id, res) {...}
Or you can have one function and check if an id is provided
this.get = function(id, res) {
if ( Number.isInteger(id) ) {
// return the user
} else {
res = id;
// return all users
}
}

ExpressJs render after an action

i'm testing ExpressJs and i have a problem.
var mysql = require('mysql');
var url = require('url');
var connection = mysql.createConnection({
host : 'localhost',
port : '8889',
user : 'root',
password : 'root',
database : 'test'
});
var results = '';
// INIT
exports.init = function(req, res) {
if (req.params.query == 'names') {
getByName(req, res);
} else {
res.send('Erreur');
}
}
getByName = function(req, res) {
currentUrl = url.parse(req.url);
getResult = req.params.suffix.split('+');
for (key in getResult) {
connection.query('SELECT * from testnode WHERE nom = "'+getResult[key]+'"', function(err, rows, fields) {
if (err) throw err;
results += JSON.stringify(rows[0]);
console.log(results);
});
}
res.render('api', {'results' : results});
}
When i go for the first time on the page this one is empty and if i refresh the result appear.
I don't know why the first time the variable "results" are empty so the console.log give me the good result.
Have you got any ideas ?
Thanks a lot :)
You error comes from the mix of a loop and a callback. Node.js is a non blocking IO library : the process doesn't wait for your mysql query to finish to continue to do other stuffs, so the callback with the results is executed (sometime) after the render.
You have multiple options, the one I use is https://github.com/caolan/async or call render once all the callback are done.
Or change your strategy:
getByName = function(req, res) {
currentUrl = url.parse(req.url);
getResult = req.params.suffix.split('+');
connection.query('SELECT * from testnode WHERE nom IN ("' + getResult.join('",") + '")', function(err, rows, fields) {
if (err) throw err;
var results = JSON.stringify(rows); //get all the results
res.render('api', {'results' : results});
});
}