passing Tedious connection as parameter - sql

I am trying to use a simple suite of functions built utilizing the Tedious library to access a Microsoft SQL Server. Here is my "tools" file:
'use strict';
const tedious = require('tedious');
const q = require('q');
var Connection = tedious.Connection;
var Request = tedious.Request;
module.exports = {
connectSQL : function(config) {
var connection = new Connection(config);
connection.on('connect', function(err) {
if (err) {
console.log('FAIL ON CONNECT');
console.log(err);
} else {
try {
/* ----- */
return connection;
} catch (err) {
console.log(err);
return;
}
}
});
connection.on('error', function(err) {
if (err) {
console.log('FAIL ON ERROR');
console.log(err);
} else {
console.log("Error called with no err object.");
}
});
},
executeSQL: function(connection, requestString) {
var results = [];
var request = new Request( requestString , function(err, data) {
if (err) {
console.log(err);
} else {
console.log( data );
}
});
request.on('row', function(row) {
//console.log(row);
results.push( row );
});
request.on('requestCompleted', function(){
console.log('Finished');
return results;
});
connection.execSql(request);
}
}
I call these functions as follows in my server file.
const sqlTools = require('./sqlTools.js');
var connection = sqlTools.connectSQL(config);
sqlTools.executeSQL(connection, "select * from dbo.test");
However, I get the error "TypeError: Cannot read property 'execSql' of undefined", even if I make the program sleep for 10 seconds before calling my function sqlTools.executeSQL (obviously not ideal).
I was able to get this to work by calling the request within the sqlTools.connectSQL function (at the "/* ----- */"), but I want to re-use the Tedious connection to make multiple calls. Any suggestions? Thanks!
~~~~~~~EDIT~~~~~~~~~~
With help from akinjide I was able to implement callbacks that allow me to make a single call to my SQL database. However, I am struggling to implement promises to make subsequent calls. I changed my "tools" file as such:
'use strict';
const tedious = require('tedious');
const q = require('q');
var Connection = tedious.Connection;
var Request = tedious.Request;
module.exports = {
connectSQL: function(config) {
var deferred = q.defer();
var connection = new Connection(config);
connection.on('connect', function(err) {
if (err) {
deferred.reject( err );
} else {
deferred.resolve( connection );
}
});
connection.on('error', function(err) {
deferred.reject(err);
});
return deferred.promise;
},
executeSQL: function(connection, requestString, callback) {
var results = [];
const request = new Request(requestString, function(err) {
callback(err);
});
request.on('row', function(row) {
results.push(row);
});
request.on('requestCompleted', function() {
console.log('request completed!');
callback(null, results);
});
connection.execSql(request);
}
}
and I call this code like this...
var promise = sqlTools.connectSQL(config);
promise.then(function (connection) {
sqlTools.executeSQL(connection, "select * from dbo.test", function(err, results) {
if (err) {
console.log(err);
}
console.log(results);
});
}).catch(function (err) {
console.log(err);
}).then(function (connection) {
sqlTools.executeSQL(connection, "select * from dbo.test2", function(err, results) {
if (err) {
console.log(err);
}
console.log(results);
});
}).catch(function(err) {
console.log(err);
});
This returns the first call's results correctly, but unfortunately returns this error "TypeError: Cannot read property 'execSql' of undefined" for the second call as it is not recognizing the connection the second time around. Any suggestions?

A better approach would be to pass a node.js callback style function as an argument to connectSQL.
return keyword won't work within an asynchronous program.
'use strict';
const tedious = require('tedious');
const Connection = tedious.Connection;
const Request = tedious.Request;
module.exports = {
connectSQL: function(config, callback) {
const connection = new Connection(config);
connection.on('connect', function(err) {
if (err) {
callback(err);
} else {
callback(null, connection);
}
});
connection.on('error', function (err) {
callback(err);
});
},
executeSQL: function(connection, requestString, callback) {
let results = [];
const request = new Request(requestString, function(err) {
callback(err);
});
request.on('row', function(row) {
results.push(row);
});
request.on('requestCompleted', function(){
console.log('Finished');
callback(null, results);
});
connection.execSql(request);
}
}
Then you can require, use sqlTools.connectSQL passing two parameters config and function(err, connection) {}
const sqlTools = require('./sqlTools');
sqlTools.connectSQL(config, function(err, connection) {
if (err) {
console.log('FAIL ON CONNECT');
console.log(err);
}
sqlTools.executeSQL(connection, "select * from dbo.test", function (err, results) {
if (err) {
console.log(err);
}
console.log(results);
});
});

Related

Query works but cant retrieve the data

I am new to Node.js (3 days total experience). I am using Node.js and the tedious package to query a database (azure SQL). I use the example as explained here: https://learn.microsoft.com/en-us/azure/azure-sql/database/connect-query-nodejs?tabs=macos
const connection = new Connection(config);
// Attempt to connect and execute queries if connection goes through
connection.on("connect", err => {
if (err) {
console.error(err.message);
} else {
console.log("Reading rows from the Table...");
// Read all rows from table
const request = new Request(
"SELECT * FROM clients",
(err, rowCount, columns) => {
if (err) {
console.error(err.message);
} else {
console.log(`${rowCount} row(s) returned`);
}
}
);
request.on("row", columns => {
columns.forEach(column => {
console.log("%s\t%s", column.metadata.colName, column.value);
});
});
connection.execSql(request);
}
});
I have two issues:
I do not know how to get the queried data into an object and
If I run the script it does print the items to the console, but it doesn't close the connection after it has done so. If I add a connection.close() at the bottom, it will close the connection before its done. I get the feeling that node.js executes everything at the same time (I am used to Python..).
Update
I found a way to close the connection, to my understanding the request object has several "events" that are predefined by the library. It seems I need to add the event "done" through request.on('done', ...) in order to make sure that it can even BE done. My updated code looks like this:
var connection = new Connection(config);
connection.connect(function(err) {
// If no error, then good to go...
executeStatement();
}
);
connection.on('debug', function(text) {
//remove commenting below to get full debugging.
//console.log(text);
}
);
function executeStatement() {
request = new Request("SELECT * FROM clients", function(err, rowCount) {
if (err) {
console.log(err);
} else {
console.log(rowCount + ' rows');
}
connection.close();
});
request.on('row', function(rows) {
_.forEach(rows, function(value, collection){
console.log(value)
console.log(value.value);
console.log(value.metadata.colName)
console.log(collection)
})
});
request.on('done', function(rowCount, more) {
console.log(rowCount + ' rows returned');
});
// In SQL Server 2000 you may need: connection.execSqlBatch(request);
connection.execSql(request);
}
Anyways, your help would be much appreciated!
Regards
Pieter
The package tedious is synchronous package, it uses the callback to return results. So when we call connection.close(), it will disable connection and stop the callback function. If will want to close the connection, I suggest you use async package to implement it.
For example
const { Connection, Request } = require("tedious");
const async = require("async");
const config = {
authentication: {
options: {
userName: "username", // update me
password: "password", // update me
},
type: "default",
},
server: "your_server.database.windows.net", // update me
options: {
database: "your_database", //update me
encrypt: true,
validateBulkLoadParameters: true,
},
};
const connection = new Connection(config);
let results=[]
function queryDatabase(callback) {
console.log("Reading rows from the Table...");
// Read all rows from table
const request = new Request("SELECT * FROM Person", (err, rowCount) => {
if (err) {
callback(err);
} else {
console.log(`${rowCount} row(s) returned`);
callback(null);
}
});
request.on("row", (columns) => {
let result={}
columns.forEach((column) => {
result[column.metadata.colName]=column.value
console.log("%s\t%s", column.metadata.colName, column.value);
});
// save result into an array
results.push(result)
});
connection.execSql(request);
}
function Complete(err, result) {
if (err) {
callback(err);
} else {
connection.close();
console.log("close connection");
}
}
connection.on("connect", function (err) {
if (err) {
console.log(err);
} else {
console.log("Connected");
// Execute all functions in the array serially
async.waterfall([queryDatabase], Complete);
}
});
connection.connect();
Besides, you also can use the package mssql. It supports asynchronous methods and depends on package tedious. We can directly call close after querying.
For example
const mssql = require("mssql");
const config = {
user: "username",
password: "password",
server: "your_server.database.windows.net",
database: "your_database",
options: {
encrypt: true,
enableArithAbort: true,
},
};
let pool = new mssql.ConnectionPool(config);
async function query() {
try {
await pool.connect();
const request = pool.request();
const result = await request.query("SELECT * FROM Person");
console.dir(result.recordset);
await pool.close();
console.log(pool.connected);
} catch (error) {
throw error;
}
}
query().catch((err) => {
throw err;
});
You can custom a class first and declare an Array to save ojects such as:
let sales = new Array();
class SalesLT{
constructor(catagryName,productName){
this.catagryName = catagryName;
this.productName = productName;
}
Here my sql statement returns 2 properties, so every time the loop takes out two elements from the ColumnValue[].
request.on("row", columns => {
for(let i=0; i<columns.length; i=i+2){
let sale = new SalesLT(columns[i].value,columns[i+1].value);
sales.push(sale);
}
sales.forEach( item => {
console.log("%s\t%s",item.catagryName, item.productName)
})
});
The code is as follows:
const { Connection, Request } = require("tedious");
let sales = new Array();
class SalesLT{
constructor(catagryName,productName){
this.catagryName = catagryName;
this.productName = productName;
}
}
// Create connection to database
const config = {
authentication: {
options: {
userName: "<***>", // update me
password: "<***>" // update me
},
type: "default"
},
server: "<****>.database.windows.net", // update me
options: {
database: "<***>", //update me
encrypt: true
}
};
const connection = new Connection(config);
// Attempt to connect and execute queries if connection goes through
connection.on ("connect", err => {
if (err) {
console.error(err.message);
} else {
queryDatabase();
}
});
function queryDatabase() {
console.log("Reading rows from the Table...");
// Read all rows from table
const request = new Request(
`SELECT TOP 2 pc.Name as CategoryName,
p.name as ProductName
FROM [SalesLT].[ProductCategory] pc
JOIN [SalesLT].[Product] p ON pc.productcategoryid = p.productcategoryid`,
(err, rowCount) => {
if (err) {
console.error(err.message);
} else {
console.log(`${rowCount} row(s) returned`);
}
connection.close();
}
);
request.on("row", columns => {
for(let i=0; i<columns.length; i=i+2){
let sale = new SalesLT(columns[i].value,columns[i+1].value);
sales.push(sale);
}
sales.forEach( item => {
console.log("%s\t%s",item.catagryName, item.productName)
})
});
connection.execSql(request);
}
this article should help you, to solve all the issues you are facing...which were the same I had when I started using Node :)
https://devblogs.microsoft.com/azure-sql/promises-node-tedious-azure-sql-oh-my/

Using asynchronous methods but Express still returning 503 (NodeJS)

I'm developing a web application in NodeJS with an Express back-end. The application is running smoothly except that when a user signs up, a somewhat long operation is called on the back end that involves saving to a database, resizing an image, etc. It takes a few seconds for this process to complete, and during this time anyone else who makes a request to the server will receive a 503 error and will be unable to do anything, whether that's sending or receiving data. I am using asynchronous functions in order to do this whole process. I am using multer to read the file, fs to read and write the file, and Jimp to resize the image. The code is as shown below.
module.exports = function(router) {
router.route('/')
.post(function(req, res, next) {
upload(req, res, function(err) {
var salt = bcrypt.genSaltSync(saltRounds);
var identifier = makeIdentifier(req.body.first_name.trim())
let emailNotifications = 0;
if (req.body.email_notifications === 'true') {
emailNotifications = 1;
}
var signup = {
identifier: identifier,
first_name: toTitleCase(req.body.first_name.trim()),
last_name: req.body.last_name.trim(),
hobby: req.body.hobby,
type: 'user',
email: req.body.email.trim(),
email_notifications: emailNotifications,
password: bcrypt.hashSync(req.body.password, salt)
};
let ascii = /^[ -~]+$/;
for (var propertyName in signup) {
if (!ascii.test(signup[propertyName])) {
res.json({
reason: "invalid-characters"
});
return;
}
}
if (req.file.size > 5000000) {
res.json({
reason: "file-size"
});
return;
}
let emailExists = false;
db.doesEmailExist(req.body.email, function(err, results) {
if (err) {
res.status(500).send("Server error");
return;
} else {
if (results.length > 0) {
res.json({
reason: "email-exists"
});
return;
} else {
fs.readFile(req.file.path, function(err, data) {
let newPath = __dirname + "/profile-pictures/" + identifier + ".png";
fs.writeFile(newPath, data, function(err) {
if (err) {
console.log(err);
} else {
try {
Jimp.read(newPath, (err, file) => {
if (err) throw err;
file
.resize(300, 300) // resize
.quality(60) // set JPEG quality
.write(newPath); // save
});
} catch (error) {
res.json({
reason: "image-properties"
});
return
}
}
});
});
db.signup(signup, function(err, results) {
if (err) {
res.status(500).send("Server error");
return;
} else {
res.json({
success: true
});
return;
}
})
}
}
})
})
});
}
What is causing the server to respond with a 503 error? Any help would be appreciated, thanks!

Using async / await with mongoose.js

Looking for help to rewrite these 2 queries using async / await instead of using the nested callbacks approach.
exports.post_edit_get = function(req, res, next) {
var id = req.params.id;
if (mongoose.Types.ObjectId.isValid(id)){
POST.findById(id, function (err, doc){
if (err) { return next(err); }
playerQuery.exec(function (err, players){
if (err) { return next(err); }
res.render('posts/posts_admin', { title: pageTitle, formData: doc, players: players });
});
});
}else{
res.send("Invalid ID");
};
};
Here you go
const { isValid } = mongoose.Types.ObjectId
exports.post_edit_get = async function(req, res, next) {
var { id } = req.params;
if (!isValid(id)){
return res.send("Invalid ID");
}
try {
const post = await POST.findById(id)
const players = await playerQuery.exec()
res.render('posts/posts_admin', {
title: pageTitle,
formData: doc,
players: players
})
} catch (err) {
return next(err)
}
}
If you want to get rid of these try/catches at the route handler level you'll want to have a look at this post; Using async/await to write cleaner route handlers

Express "Can't set headers after they are sent" after deploying to Heroku

after deploying my server code to Heroku I'm getting the error "Can't set headers after they are sent". I fetch my login data
fetch("url", {
method: "POST",
mode: "cors",
headers: {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded"
//"Content-Type": "application/json"
},
body: requestBody
}).then((res, next) => {
if(res.ok){
res.json().then((json) => {
if(json.verifystate){
this.props.navigation.navigate('Home')
}
else{
this.setState({isAuthentic:false});
}
});
}else{
next();
}
})
And it calls my server post method
app.post('/users/auth', function(req, res) {
loginData(db, req.body.email, req.body.password, req.body.mphone, function(result){
if(result == 1){
res.send({"verifystate":1});
}else if(result == 2){
res.send({"verifystate":2});
}else{
res.send({"verifystate":3});
}
});
//console.log('json: '+JSON.stringify(data));
});
edit: Login function
module.exports = function loginData(db, email, myPlaintextPassword, mphone, callback){
var collectionUser = db.collection('users');
bcrypt.genSalt(saltRounds, function(err, salt) {
bcrypt.hash(myPlaintextPassword, salt, function(err, hash) {
var queryStr = {"emails.address": email};
collectionUser.findOne(queryStr, function(err, result) {
if(err)
{
return;
}else if(result != null){
bcrypt.compare(myPlaintextPassword, result.services.password.bcrypt, function(err, res) {
console.log("conpare result: ", res);
if(res){
callback("1");
}else{
callback("3");
}
});
if(!result.services.resume || result.services.resume.appLoginToken == null
|| !result.services.resume.appLoginToken.date || !result.services.resume.appLoginToken.base64Token){
var tokenStr = email + Math.random(1,100);
tokenStr = Base64.encode(tokenStr);
queryStr = {
"services.resume.appLoginToken.date": Date(),
"services.resume.appLoginToken.base64Token": tokenStr
};
var newDate = { "services.resume.appLoginToken.date": Date() }
var temp = {"emails.address": email};
collectionUser.update(temp,
{ $set: {"services.resume.appLoginToken.date": Date()} }, function(err, result) {
if(err)
{
return;
}else {
}
});
collectionUser.update(temp,
{ $set: {"services.resume.appLoginToken.base64Token": tokenStr} }, function(err, result) {
if(err)
{
return;
}else {
}
});
}else{
console.log("already have token");
}
}else{
callback("2")
}
});
});
});
};
And when it gets to res.send({"verifystate:"1}) it then throws the aforementioned error. Any help would be great thanks.
if(!res.headersSent){
res.send('hello world');
}
or
if(!res.headersSent){
res.json({message: 'hello world'});
}
you probably have a callback that fires more than once, often times this is with event emitters.
As a side note, I really recommend using 2 spaces for indentation with JS, because so much code is nested. And I also recommend putting else and .then() on a newline, more readable.
I did not read the whole code but you could try this:
module.exports = function loginData(db, email, myPlaintextPassword, mphone, callback) {
let collectionUser = db.collection('users');
bcrypt.genSalt(saltRounds, function (err, salt) {
bcrypt.hash(myPlaintextPassword, salt, function (err, hash) {
let queryStr = {"emails.address": email};
collectionUser.findOne(queryStr, function (err, result) {
if (err) {
//You should do something here or it would take you to an error , just an return won't work, you've to call a callback that send the error message
//callback(0)
} else if (result !== null) {
if (!result.services.resume || result.services.resume.appLoginToken === null
|| !result.services.resume.appLoginToken.date || !result.services.resume.appLoginToken.base64Token) {
let tokenStr = email + Math.random(1, 100), temp = {"emails.address": email};
tokenStr = Base64.encode(tokenStr);
queryStr = {
"services.resume.appLoginToken.date": new Date(),
"services.resume.appLoginToken.base64Token": tokenStr
};
collectionUser.update(temp,
{$set: {"services.resume.appLoginToken.date": new Date()}}, function (err, result) {
});
collectionUser.update(temp,
{$set: {"services.resume.appLoginToken.base64Token": tokenStr}}, function (err, result) {
});
} else {
console.log("already have token");
}
bcrypt.compare(myPlaintextPassword, result.services.password.bcrypt, function (err, res) {
console.log("conpare result: ", res);
if (res) {
callback(1);
} else {
callback(3);
}
});
} else {
callback(2);
}
});
});
})
};
And this:
app.post('/users/auth', function (req, res) {
loginData(db, req.body.email, req.body.password, req.body.mphone, function (result) {
res.send({verifystate: result});
});
});

ExpressJS Multer: Upload image to server

I'm newer with Node.js and Express.js.
I want to upload first a image into the server (directory: uploads/spots), and then (synchronous) upload the rest of form data in MongoDB.
I'm using REST (Method Post)
app.route('/spots').post(users.requiresLogin, spots.create);
and I'm using Multer for updating the image into the server, and works.
app.use(multer(
{ dest: './public/uploads/spots',
onFileUploadStart: function (file) {
var imagePath = file.path;
gm(imagePath).resize(850, 850).quality(70).noProfile().write('public/uploads/spots/850x850/'+file.name, function (err) {
if (!err) {
gm(imagePath).resize(150, 150).quality(70).noProfile().write('public/uploads/spots/150x150/'+file.name, function (err) {
if (!err) {
}
else{
console.log('Error: '+err);
}
});
}
else{
console.log('Error: '+err);
}
});
}
}));
Is working, but is asynchronous , and returns the response to frontend before that the image will be upload into the server.
My question is how to do this but synchronous and how to return the response to the frontend after that the image was uploaded.
Thank you!
spots.server.routes.js
'use strict';
module.exports = function(app) {
var gm = require('gm');
var multer = require('multer');
var users = require('../controllers/users.server.controller.js');
var spots = require('../controllers/spots.server.controller.js');
//Upload image
app.use(multer(
{ dest: './public/uploads/spots',
onFileUploadStart: function (file) {
var imagePath = file.path;
gm(imagePath).resize(850, 850).quality(70).noProfile().write('public/uploads/spots/850x850/'+file.name, function (err) {
if (!err) {
gm(imagePath).resize(150, 150).quality(70).noProfile().write('public/uploads/spots/150x150/'+file.name, function (err) {
if (!err) {
}
else{
console.log('Error: '+err);
}
});
}
else{
console.log('Error: '+err);
}
});
}
}));
// Spots Routes
app.route('/spots')
.get(spots.list)
.post(users.requiresLogin, spots.create);
app.route('/spots/:spotId')
.get(spots.read)
.put(users.requiresLogin, spots.update)
.delete(users.requiresLogin, spots.hasAuthorization, spots.delete);
// Finish by binding the Spot middleware
app.param('spotId', spots.spotByID);
};
spots.server.controller.js (create method)
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
errorHandler = require('./errors.server.controller.js'),
Spot = mongoose.model('Spot'),
_ = require('lodash'),
fs = require('fs');
/**
* Create a Spot
*/
exports.create = function(req, res) {
var spot = new Spot(JSON.parse(req.body.spot));
spot.user = req.user;
if(req.files.file)
spot.image=req.files.file.name;
else
spot.image='default.jpg';
spot.save(function(err) {
if (err) {
fs.unlinkSync('public/uploads/spots/'+spot.image);
fs.unlinkSync('public/uploads/spots/850x850/'+spot.image);
fs.unlinkSync('public/uploads/spots/150x150/'+spot.image);
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
var socketio = req.app.get('socketio'); // tacke out socket instance from the app container
socketio.sockets.emit('spot.created.'+spot.municipality, {spot:spot, user:req.user});
socketio.sockets.emit('spot.created.'+spot.province, {spot:spot, user:req.user});
socketio.sockets.emit('spot.created.'+spot.community, {spot:spot, user:req.user});
socketio.sockets.emit('spot.created.'+spot.country, {spot:spot, user:req.user});
res.jsonp(spot);
}
});
};
/**
* Spot authorization middleware
*/
exports.hasAuthorization = function(req, res, next) {
if (req.spot.user.id !== req.user.id) {
return res.status(403).send('User is not authorized');
}
next();
};
The solution is not use onFileUploadStart method and use a function with callback in the controller.
routes
// Spots Routes
app.route('/spots')
.get(spots.list)
.post(users.requiresLogin,multer({ dest: './public/uploads/spots'}), spots.create);
controller
exports.create = function(req, res) {
if (req.files.file)
exports.uploadImage(req.files.file,callback);
else
callback();
function callback(){
var spot = new Spot(JSON.parse(req.body.spot));
spot.user = req.user;
if (req.files.file)
spot.image = req.files.file.name;
else
spot.image = 'default.jpg';
spot.save(function (err) {
if (err) {
fs.unlink('public/uploads/spots/850x850/'+spot.image);
fs.unlink('public/uploads/spots/150x150/'+spot.image);
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
var socketio = req.app.get('socketio'); // tacke out socket instance from the app container
socketio.sockets.emit('spot.created.' + spot.municipality, {spot: spot, user: req.user});
socketio.sockets.emit('spot.created.' + spot.province, {spot: spot, user: req.user});
socketio.sockets.emit('spot.created.' + spot.community, {spot: spot, user: req.user});
socketio.sockets.emit('spot.created.' + spot.country, {spot: spot, user: req.user});
req.spot = spot;
Feedback.subscribeSpot(req);
Notify.getLocalSubscriptors(spot.municipality,spot.province,spot.community,spot.country,function(subscriptions){
Notify.create(req,null,spot,null,null,null,subscriptions,'spots/'+spot._id,false,'SPOT_CREATED', function(){
res.jsonp(spot);
});
});
}
});
}
};
exports.uploadImage = function(file, fn){
var imagePath = file.path;
gm(imagePath).resize(850, 850).quality(70).noProfile().write('public/uploads/spots/850x850/'+file.name, function (err) {
if (!err) {
gm(imagePath).resize(150, 150).quality(70).noProfile().write('public/uploads/spots/150x150/'+file.name, function (err) {
if (!err) {
if(fn)fn();
}
else{
console.log('Error: '+err);
}
});
}
else{
console.log('Error: '+err);
}
});
};