When I load my page I want some products to be shown, so I make a GET request and it retrieves them from the database. However, when I refresh the page I notice the old connection remains. How to make sure the old connections close?
Here's my code:
const MongoClient = require('mongodb').MongoClient;
const connection = (closure) => {
return MongoClient.connect(config.connectionString, (err, client) => {
if (err) {
return winston.log('error', now() + err);
}
closure(client);
});
};
...
router.get('/products', (req, res) => {
connection((client) => {
client.db('dbname').collection('collectionname')
.find({})
.toArray()
.then((products) => {
response.data = products;
response.message = "Products retrieved successfully!"
res.json(response);
})
.catch((err) => {
winston.log('error', now() + err);
sendError(err, res);
});
});
});
Well, each time your /products route is called, you do create a new MongoClient instance. In that extent to limit the number of connection to your Database, you may either connect once, and save your MongoClient instance:
let client = undefined;
const connection = (closure) => {
// Return the client if any...
if(client) return closure(client);
return MongoClient.connect(config.connectionString, (err, c) => {
if (err) {
return winston.log('error', now() + err);
}
// Save the client.
client = c;
closure(client);
});
};
...or simply close the MongoClient connection you instantiated once you're done with it:
router.get('/products', (req, res) => {
connection((client) => {
client.db('dbname').collection('collectionname')
.find({})
.toArray()
.then((products) => {
response.data = products;
response.message = "Products retrieved successfully!"
// Close the MongoClient...
client.close();
res.json(response);
})
.catch((err) => {
winston.log('error', now() + err);
sendError(err, res);
// Close the MongoClient...
client.close();
});
});
});
I would advise you to go with the first solution: The MongoClient maintains a connection pool, so having multiple clients does not have any advantages. In addition, it allows you to check whether or not the DB is remotely available, before executing anything else as well (just connect to the DB on your app init(), and save the client instance, and you'll be done).
Related
I am writing a new method that connects to the database, runs SQL queries and closes that connection every time a request is made from front-end. Connection to database is established and closed again and again for every query that I want to run. Does this impact the performance? Is this the right way or there is a better way to do this?
app.post('/register', (req, res) =>{
registerDb().then(resp => {
res.json({"message" : resp})})
.catch(err => {
console.log(err);
})
})
app.post('/signin', (req, res) => {
checkAuth(req.body).then(response => {
res.send(response);
})
.catch(err => {
console.log(err);
});
})
app.listen(4000);
async function registerDb() {
let conn;
try {
conn = await oracledb.getConnection(config)
let result = await conn.execute(
`INSERT INTO "User" VALUES (name, email, id, password, age)`,
);
console.log("Rows inserted: " + result.rowsAffected); // 1
console.log("ROWID of new row: " + result.lastRowid);
return result.rowsAffected;
} catch (err) {
console.log('Ouch!', err)
return err.message;
} finally {
if (conn) { // conn assignment worked, need to close
await conn.close()
}
}
}
async function checkAuth(data) {
let conn;
try {
conn = await oracledb.getConnection(config)
let result = await conn.execute(
`Select name
from "User"
where email = :email and password = :password`,
{
email : {val: data.email},
password: {val: data.password}
}
);
return result.rows;
} catch (err) {
console.log('Ouch!', err)
return err.message;
} finally {
if (conn) { // conn assignment worked, need to close
await conn.close()
}
}
}
Opening and closing connections to the DB impacts performance. A process needs to be started on the DB host, memory has to be allocated and initialized. And the reverse at connection close. Since these are new connections, they can't reuse some cached data for statement execution.
From the node-oracledb pooling documentation:
When applications use a lot of connections for short periods, Oracle recommends using a connection pool for efficiency.
Review that manual, and look at examples like webapp.js.
Make sure you increase UV_THREADPOOL_MAX for apps that have multiple connections open.
I'm using a Node.js server to make requests to an Azure sql database.
As far as I understand the following function does not prevent sql injection:
Current code: (working but unsafe)
var executeQuery = async function(query, response) {
const pool = new sql.ConnectionPool(dbConfig)
pool.on('error', err => {
console.log('sql errors', err);
});
try {
await pool.connect();
let result = await pool.request().query(query);
response.send(result.recordset);
return {success: result}
} catch (err) {
return {err: err};
} finally {
console.log('request complete')
pool.close(); // closing connection after request is finished
}
};
app.get("/api/workOrders/byId/:workOrderId", function(req, res) {
console.log(req.params);
var query = "SELECT * FROM [WorkOrder] WHERE [idWorkOrder]=" + req.params.workOrderId;
executeQuery(query, res);
});
I would like to have the executeQuery function standalone, but I did not find an answer for that yet. Anyway, this is the code I constructed from mssql documentation:
New Code (not working)
app.get("/api/test/:workOrderId", function(req, res) {
console.log(req.params.workOrderId);
(async function() {
const pool = new sql.ConnectionPool(dbConfig)
pool.on('error', err => {
console.log('sql errors', err);
});
try {
await pool.connect();
let result = await pool.request()
.input('input_parameter', sql.VarChar(50), req.params.workOrderId)
.query('SELECT * FROM [Quotation] WHERE [idWorkOrder]= #input_parameter');
console.log(result);
res.send(result.recordset);
return {success: result}
} catch (err) {
return {err: err};
} finally {
console.log('request complete')
pool.close(); // closing connection after request is finished
}
});
})
This version should be injection proof, but It does not return anything. Is there an option to pass the input values to the executeQuery function as in the current code?
You can pass the value of req.params.workOrderId into your async function and then use that value inside. check the following code.
app.get("/api/test/:workOrderId", function(req, res) {
console.log(req.params.workOrderId);
(async function(workOrderId) {
const pool = new sql.ConnectionPool(dbConfig)
pool.on('error', err => {
console.log('sql errors', err);
});
try {
await pool.connect();
let result = await pool.request()
.input('input_parameter', sql.VarChar(50), workOrderId)
.query('SELECT * FROM [Quotation] WHERE [idWorkOrder]= #input_parameter');
console.log(result);
res.send(result.recordset);
return {success: result}
} catch (err) {
return {err: err};
} finally {
console.log('request complete')
pool.close(); // closing connection after request is finished
}
})(req.params.workOrderId); // <===pass value to the function
})
I am JS newbie so this may be some silly trouble. I have a lambda written in NodeJS 10.x and I am trying to add MongoDB Atlas insertion. I have started with this tutorial: https://docs.atlas.mongodb.com/best-practices-connecting-to-aws-lambda/
This is my code:
const MongoClient = require('mongodb').MongoClient;
let cachedDb = null;
function connectToDatabase (uri) {
console.log('Connect to mongo database');
if (cachedDb) {
console.log('Using cached database instance');
return Promise.resolve(cachedDb);
}
return MongoClient.connect(uri)
.then(db => {
console.log('Successful connect');
cachedDb = db;
return cachedDb;
}).catch(err => {
console.log('Connection error occurred: ', err);
callback(err);
});
}
function insertUser(db, email) {
console.log('=> modify database');
return db.collection('users').insertOne({"email" : email})
.then(() => { callback(null, result); })
.catch(err => {
console.log('Insert error occurred: ', err);
callback(err);
});
}
exports.handler = (payload, context, callback) => {
const { email, password } = JSON.parse(payload.body);
context.callbackWaitsForEmptyEventLoop = false;
connectToDatabase(MONGODB_URI)
.then(db => {
console.log('Mongo connected')
insertUser(db, email);
})
.then(result => {
console.log('Mongo insert succeeded', result);
})
.catch(err => {
console.log('Mongo insert failed', err);
return responses.INTERNAL_SERVER_ERROR_500(err, callback, response);
});
console.log('finished mongo stuff');
I can see the following logs in CloudWatch:
START RequestId: 0338d336-7d33-40d5-abc7-1511f1c9ea4c Version: $LATEST
2020-01-11T12:18:00.808Z 0338d336-7d33-40d5-abc7-1511f1c9ea4c INFO Connect to mongo database
2020-01-11T12:18:00.855Z 0338d336-7d33-40d5-abc7-1511f1c9ea4c INFO finished mongo stuff
2020-01-11T12:18:01.416Z 0338d336-7d33-40d5-abc7-1511f1c9ea4c ERROR (node:8) DeprecationWarning: current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.
END RequestId: 0338d336-7d33-40d5-abc7-1511f1c9ea4c
The item is not inserted in Atlas. I have added more verbose logging but it is not shown. If the mongo connect failed there shall be an error. But it seems that the error handlers are ignored. Where is the problem?
You are very close. Few things are missing
callback function needs to be called from your handler function as the insert function doesn't know about callback.
When you do a .then on a promise and you use curly braces you need to return whatever is in there to send it to the next then (unless you do it in a single line).
db in insert method needs to be db.db()
I would recommend to to use async/await instead of callback style. Here is the equivalent code
exports.handler = async (payload) => {
try {
const { email, password } = JSON.parse(payload.body);
const db = await connectToDatabase(MONGODB_URI);
console.log("Mongo connected");
const result = await insertUser(db, email);
console.log("Mongo insert succeeded", result);
return result;
} catch(err) {
console.error(err);
}
};
I have the following async method:
alreadyLoaded: async function (id) {
const pool = await poolPromise;
return pool.request()
.input('idParameter', id)
.query('SELECT count(*) AS value FROM dbo.partidos WHERE id=#idParameter')
.then(result => {
console.log(result.recordset[0].value)
result.recordset[0].value > 0
}).catch(function(err) {
console.log(err.message);
});
Invoked in another one:
processMatches: function(payload) {
payload.matches.forEach(p => {
if(partidosRepository.alreadyLoaded(p.id))
{
console.log("El partido ya fue cargado.");
return;
}
The alreadyLoaded method checks if some record is already inserted on database, and it's invoked inside another method to validate. The problem here is that the processMatches method continues processing records before the alreadyLoaded finishs with the current one.
I'm having a wrong manage of promises here, Can anyone help me solve this out?
Here's the database connection:
const poolPromise = new sql.ConnectionPool(config)
.connect()
.then(pool => {
console.log('Connected to localhost '+ config.database +' database');
return pool;
})
.catch(err => console.log('Database connection failed. Error: ', err));
module.exports = {
sql, poolPromise
}
Why don't you build your if statement like this
if(await partidosRepository.alreadyLoaded(p.id))
also keep in mind you are not returning boolean value here
}).catch(function(err) {
console.log(err.message);
});
0) I have an HTTP trigger:
exports.checkout = functions.https.onRequest((req, res) => {
1) Update top up transactions when user buys topUp package:
admin.database().ref('topupTransaction').push(topUpObject)
2) Get the user object (with account balance)
admin.database().ref('/users/' + userID).once("value",snap=> {
3) Set the new user object (with new account balance)
admin.database().ref('/users/' + req.query.userUid).set(topUpObject);
I'm not sure how to run all these (1,2,3) sequentially and return a value to the client (0). Hitting shouldn't embed promise. There's Promise.all, but how to use it in this case.
Hitting "Avoid nesting promises." when I try to do it in this way:
exports.checkout = functions.https.onRequest((req, res) => {
var nonceFromTheClient = req.body.payment_method_nonce;
var topUpObject = {
amount : parseInt(req.query.topUpPackage),
date : admin.database.ServerValue.TIMESTAMP, // 1525451616097
user : req.query.userUid
};
admin.database().ref('topupTransaction').push(topUpObject)
.then((topUpResult) => {
return admin.database().ref('/users/' + userID).once("value");
}).then((oldUserData)=>{
return admin.database().ref('/users/' + req.query.userUid).set(topUpObject).then((newUserData)=>{
return res.send(newUserData.val());
})
;
}).catch((error) => {
// Update databse failed (top up transaction)
console.log('Error sending message:', error);
return res.status(500).send(error);
});
Update
using Promise.all, but working partially with errors:
// Create 2 functions
function asyncFunction1(topUpObject){
// Push top up object to database
admin.database().ref('topupTransaction').push(topUpObject)
.then((response) => {
// Update databse successful (top up transaction)
console.log('Top Up transaction created successfully!', topUpObject);
// return res.redirect(303, response.ref);
return topUpObject;
}).catch((error) => {
// Update databse failed (top up transaction)
console.log('Error sending message:', error);
return error;
});
}
function asyncFunction2(userID,topUpObject){
// Get the user account balance
console.log('Current User ID: ', userID);
var ref = admin.database().ref('users').child(userID);
admin.database().ref('/users/' + userID).once("value",snap=> {
// do some stuff once
console.log('Current User Data',snap.val());
console.log('Current User balance',snap.val().accountBalance);
var userContents = snap.val();
var currentBalance = userContents.accountBalance;
var updatedBalance = currentBalance + topUpObject.amount;
console.log('Updated Balance',updatedBalance);
userContents.accountBalance = updatedBalance;
/*Current User Data {
accountBalance: 0,
accountCurrency: 'MYR',
createdOn: '2018-05-02T20:42:49Z',
phoneNumber: '+123445555555'
}
*/
admin.database().ref('/users/' + userID).set(userContents).then(snapshot => {
console.log('Updated top up value! for user', topUpObject);
return res.send(topUpObject.amount);
}).catch((error) => {
// Update databse failed (top up transaction)
console.log('Error sending message:', error);
return error;
});
});
}
// app.post("/checkout", function (req, res) {
exports.checkout = functions.https.onRequest((req, res) => {
var nonceFromTheClient = req.body.payment_method_nonce;
// Use payment method nonce here
// Create Transaction
gateway.transaction.sale({
amount: req.query.topUpPackage,
paymentMethodNonce: nonceFromTheClient,
options: {
submitForSettlement: true
}
},(err, result) => { //TODO: What should we pass back here???
if (err) {
// If top up error (from braintree)
console.log(err.stack);
}else{
// If top up is successful
console.log('Result:',result);
console.log('Top Up Package is: ', req.query.topUpPackage);
var topUpObject = {
amount : parseInt(req.query.topUpPackage),
date : admin.database.ServerValue.TIMESTAMP, // 1525451616097
user : req.query.userUid
};
return Promise.all([asyncFunction1(topUpObject), asyncFunction2(req.query.userUid,topUpObject)]); //TODO: how to pass back res() to client???
}
// Return the error as response
return res.send(err);
});
});
exports.client_token = functions.https.onRequest((req, res) => {
// a token needs to be generated on each request
// so we nest this inside the request handler
// Need to write a function to return a client token,
// and return it back by using the res.send command
console.log('Log customerId',req.query.text);
gateway.clientToken.generate({
// customerId: req.query.text
}, (err, response) => {
// error handling for connection issues
if (err) {
console.log(err.stack);
}else{
clientToken = response.clientToken;
console.log('Log Client token:',clientToken);
return res.send(clientToken);
}
return res.send(err);
});
// return null;
});
There are, in my humble opinion, a couple of things to be fine-tuned in your code, as follows. I've commented in the code.
However, note that since we don't know from where topUpObject comes from (and what it is) it is a bit difficult to be more precise. If you share more details (the full Cloud Function code and the database structure) I may be more precise.
admin.database().ref('topupTransaction').push(topUpObject)
.then(topUpResult => {
return admin.database().ref('/users/' + userID).once("value");
})
.then(oldUserData => {
return admin.database().ref('/users/' + req.query.userUid).set(topUpObject);
//Set returns a non-null firebase.Promise containing void
})
.then(() => { //so I don't think you can get the newUserData here
//return res.send(newUserData.val()); <- and therefore you cannot do that here
//But if I understand well you want to send back to the user the topUpObject, so do as follows:
return res.status(200).send(topUpObject); //<- note the addition of status(200) here
})
.catch((error) => {
// Update databse failed (top up transaction)
console.log('Error sending message:', error);
return res.status(500).send(error);
});