Node express multiple queries in one route that depend on each other - node.js

I am just about getting familiar with node and express and programming in general, but this is a more complex issue I am trying to solve. Please if you can provide with some best practice in this kind of scenario.
I am trying to run two queries to my database where the first one is dependent on the result of the first. Q1. Return a list of ids. Q2. Return id and coord for each of the ids. I want to respond with a json object that look something like this
[
{ id: 451, coords: 'POINT(12.5574 43.8351)' },
{ id: 56, coords: 'POINT(13.5574 44.8351)' }
]
Currently I cannot get it to work, I know there is probably several issues with my example code, but I have pretty much got stuck. Maybe I am overthinking this and make it harder than it is, or bad practice in general.
How can I run multiple queries where the second use the output from the first one and then build the correct object to respond with. Any pointers would be much appreciated.
router.get('/asset/:id', (req, res) => {
let latLngOfAssets = []
// get associated assets
function getConnectionsById() {
queries.getConnectionsById(req.params.id) // return list of objects
.then(data => {
if (data) {
data.forEach(function(element) {
getLatLngofAsset(element.til_poi_id) // for each id in list call function to get coordinate
});
} else {
throw new Error('No data returned');
}
console.log(latLngOfAssets) // What I want to respond with res.json(latlngofassets)
})
}
function getLatLngofAsset(id) {
queries.getPoilatlng(id) // Return geometry as text for asset with id
.then(data =>{
let newassetobj = {}
if (data) {
newassetobj["id"] = data.rows[0].id
newassetobj["coords"] = data.rows[0].st_astext
//console.log(newassetobj) // structure of each object { id: 451, coords: 'POINT(12.5574 43.8351)' }
latLngOfAssets.push(newassetobj) // making list of objects i want to respond with
} else {
throw new Error('Did not get any poi');
}
})
}
getConnectionsById()
.catch(err => { // {message: "Cannot read property 'then' of undefined", error: {…}}
console.error('Something went wrong', err);
});
});

You've done a good job separating out the two distinct sections of your code into separate functions - what you're missing is the ability to tie them together. This portion of your code is not doing what I think you are trying to accomplish:
data.forEach(function(element) {
getLatLngofAsset(element.til_poi_id)
});
Because getLatLngofAsset() is Promise-based* you need to use it like a Promise. You first need to make getLatLngofAsset return the Promise chain it creates. Then, it can be await-ed inside getConnectionsById using an async function:
function getConnectionsById() {
queries.getConnectionsById(req.params.id)
.then(data => {
if (data) {
data.forEach(async function(element) { // <-- note the async keyword here
await getLatLngofAsset(element.til_poi_id)
});
} else {
throw new Error('No data returned');
}
console.log(latLngOfAssets) // What I want to respond with res.json(latlngofassets)
})
}
This is a start - there are a couple more things we can tackle once you understand the relationship between the functions you have declared and the Promises they create & return.

Related

How to unit test waterline model create method instance

I want to unit test a function in my sails.js application using Mocha(test framework) and Sinon(test double library). This is how the function under test looks like
function saveNotifications(notificationData, cb){
checkForDuplicates(notificationData, (err, isDuplicate) => {
if(isDuplicate) return updateNotification(notificationData, (err, updatedData)=> { cb(err, updatedData) })
// No duplicate exists, so save the notification in db
Notification.create(notification).exec((err, savedData) => {
return cb(err, savedData)
})
})
})
Although I was able to fake checkForDuplicates and add some tests for that. But when I wanted to stub(or fake) Notification.create(notification).exec method in order to check if create method was called once for particular test case, I was not able to figure out how to do that. Neither do I understand how to stub chained methods nor am I able to stub simple Notification.create object itself. I am assuming some gaps in my understanding of how to stub waterline method instances. Appreciate any help/advice.
What I have tried
I tried to do simpler things first i.e. stubbing create method
it('#saveNotifications({userId: "1", message: "Test message"}, cb)', (done) => {
sinon.stub(Notification, 'create');
saveNotifications({userId: "1", message: "Test message"}, (err, result) => {
try {
expect(err).to.not.throw;
expect(err).to.be.null;
expect(result).to.be.not.null;
sinon.assert.calledOnce(Notification.create);
sinon.restore();
done();
} catch(e){
sinon.restore();
done(e);
}
})
but this gives error
TypeError: Cannot stub non-existent property create
There are two issues you mentioned, let's sort them out one by one
Stub simple Notification.create object?
You have already written the right code for this but you are not referencing the correct model object. Instead of Notification object, use sails.models.notification i.e.
sinon.stub(sails.models.notification, 'create')
How to stub chained methods
Fake the returned value and use stub in the returned function i.e.
sinon.stub(sails.models.notification, 'create').callsFake(function(){
return {
exec: theStubbedChainFunction
}
})
var theStubbedChainFunction = sinon.stub().yields(null, { id: '1', message: 'canned response'})
// Then you can assert to test
sinon.assert.calledOnce(theStubbedChainFunction)
If you had more chained function (e.g. Notification.update(criteria).set(valuedToUpdate).fetch().exec(function(err, updatedRecords){...}), you could follow the same principal e.g.
sinon.stub(sails.models.notification, 'update').callsFake(function(){
return {
set: sinon.stub().callsFake(function(){
return {
fetch: sinon.stub().callsFake(function(){
exec: theStubbedChainFunction
})
}
})
}
})

Express API returning an unwanted "data" section in my GET all requests

I'm currently building a rest API and I'm having an unexpected output when I make a /GET request.
When i make a get request to the API, it returns
{
data: {
[{myExpectedObjects},{myExpectedObjects}]
}
}
however, I'm expecting my get request to return just the array of objects. Below is the code im using to accomplish the rest calls
Create controller
const create = (req, res) => {
let dataModel = generateModel(genericDataFromReq);
dataModel = new dataModel({
genericData,
specificData,
});
dataModel.save().then((data) => {
res.status(201).send(data);
}, (e) => {
res.status(500).send(e);
});
}
};
get all controller
const list = (req, res) => {
const dataModel = generateModel(dataToGet);
dataModel.find().then((data) => {
if (data.length === 0) {
res.status(404).send('failed');
} else {
res.status(200).send({ data });
}
}, (e) => {
res.status(500).send(e);
});
};
generate data model
function generateModel(dbCollectionName) {
try {
return generateDataModel(dbCollectionName);
} catch (e) {
return mongoosee.model(`${dbCollectionName}`);
}
}
I know the code is a bit unconventional but I've set up a generic rest API to take in different types of requests and I found this solution to be the best way of doing this.
Any ideas on why my get all request is tacking on a "data" section before my array of objects (which is what I'm actually interest in)?
I believe the issue is in this line:
else {
res.status(200).send({ data });
}
When you put curly braces around a variable, it creates a key-value pair where the key is the name of the variable and the value is the value of the variable. So get rid of the curly braces and it should work as you expect. See the parts that mention this ES2015 notation here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer

NodeJS: Handling transactions with NoSQL databases?

Consider a promise-chained chunk of code for example:
return Promise.resolve()
.then(function () {
return createSomeData(...);
})
.then(function () {
return updateSomeData(...);
})
.then(function () {
return deleteSomeData(...);
})
.catch(function (error) {
return ohFishPerformRollbacks();
})
.then(function () {
return Promise.reject('something failed somewhere');
})
In the above code, let's say something went wrong in the function updateSomeData(...). Then one would have to revert the create operation that was executed before this.
In another case, if something went wrong in the function deleteSomeData(...), then one would want to revert the operations executed in createSomeData(...) and updateSomeData(...).
This would continue as long as all the blocks have some revert operations defined for themselves in case anything goes wrong.
Only if there was a way in either NodeJs or the database or somewhere in the middle, that would revert all the transactions happening under the same block of code.
One way I can think of this to happen is by flagging all the rows in database with a transactionId (ObjectID) and a wasTransactionSuccessful(boolean), so that CRUD operations could be clubbed together with their transactionIds, and in case something goes wrong, those transactions could be simply deleted from the database in the ending catch block.
I read about rolling back transactions in https://docs.mongodb.com/manual/tutorial/perform-two-phase-commits/. But I want to see if it can be done in a more simpler fashion and in a generic manner for NoSQL databases to adapt.
I am not sure if this would satisfy your use case, but I hope it would.
let indexArray = [1, 2, 3];
let promiseArray = [];
let sampleFunction = (index) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 100, index);
});
}
indexArray.map((element) => {
promiseArray.push(sampleFunction(element));
});
Promise.all(promiseArray).then((data) => {
// do whatever you want with the results
}).catch((err) => {
//Perform your entire rollback here
});
async.waterfall([
firstFunc,
secondFunc
], function (err, result) {
if (err) {
// delete the entire thing
}
});
Using the async library would give you a much elegant solution than going with chaining.

Mongoose: write own methods with promise

I want to write some instance / static methods for a model, which uses the mongoose's API and do something before and after using the mongoose's API.
For example, I write my own Article.createArticle method, it checks the data before Article.create, and return article.toObject() after creation.
this is how I want my createArticle works:
Article.createArticle({someKeys: 'SomeData', ...})
.then(obj => {
// get plain object here
});
I tried to write something like this:
Article.Schema.static({
createArticle: function(data) {
return new Promise(function(resolve, reject){
checkDataKeys(data);
resolve(mongoose.model('Article').create(data)
.then(article => resolve(article.toObject()));
);
});
},
});
with this createArticle, I only get undefined in then,
I must get something wrong.
Also, in addition to make the createArticle work, is there any way to make the code more elegant?
Thanks.
I myself found a methods that works for me. Though I'm not very understand the mechanism, maybe I'll try to work on it later...
ArticleSchema.static({
createArticle: function(data) {
checkDataKeys(data); // pre works
return mongoose.model('Article').create(data)
.then(obj => {
return obj.toObject(); // afterworks
// here, just get the plain obj from doc
// and *return* it
});
},
});
update: after I searched something about Promise, maybe this could be better.
ArticleSchema.static({
createArticle: function(data) {
checkDataKeys(data); // pre works
return mongoose.model('Article').create(data)
.then(obj => {
return Promise.resolve(obj.toObject());
// Some say that this is totally same as directly return
})
.catch(err => {
return Promise.reject(err);
// if error, this will ensure the err will be caught
// for some async cases
});
},
});

sharing a transaction or task object between multiple queries across multiple controllers in pg-promise

I am relatively new to node.js, postgresql, promises and infact stackoverflow so apologies in advance if things sound a little disjointed!
I am currently trying to run multiple queries within chained promises spread across various controllers. I want to run all the queries within the same transaction or task to eliminate multiple connects and disconnects to the database.
I have tried the following where I am adding a student and assigning two mentors for that student. The HTTP request is routed to the student controller which adds a student via the student repository. The student repository is where the task starts and is returned to the controller which forwards it to the mentor controller and along the chain it goes...
# HttpPost("/api/students/create")
addStudent( # Req()request) {
var studentid;
var mentorids= [];
//Add the student
return this.studentRepository.addStudent(request.body.student)
.then(newStudentId => {
studentid = newStudentId;
//Add the mentors, passing the transaction object received back from
studentRepository
return this.mentorController.addMentor(request.body.schoolid, request.body.mentors, newStudentId.transaction)
.then(result => {
var data = [];
console.log(result);
for (var role in result) {
data.push({
mentorid: result[role].roleid,
studentid: studentid
});
}
//Assigns the student to mentors in the link table
return this.studentRepository.assignMentor(data)
.then(result => {
return result;
})
})
});
}
Student repository
addStudent(student): any {
return this.collection.task(t => {
return this.collection.one(this.sql.addStudent, student)
.then(studentid => {
return {
studentid: studentid.studentid,
transaction: t
}
});
})
}
Mentor controller
addMentor(institutionid: number, mentors, t): any {
var promises = [];
var mentorIds = [];
for (var role in mentors) {
promises.push(this.roleController.registerRole(institutionid,mentors[role].role,t));
}
return t.batch(promises)
.then(result => {
return Promise.resolve(result);
})
}
Role controller
# HttpPost("/api/roles/register")
registerRole(institutionid, # Req()request, t ? ) : any {
console.log(request);
return this.roleRepository.checkRoleEnrollment(institutionid, request.email, request.roletype, t)
.then(result => {
return this.roleRepository.addRoleEnrollment(institutionid, request, t)
.then(data => {
return this.roleRepository.updateRoleEnrollment(data.roleenrollmentid, data.roleid)
.then(d => {
return data;
})
})
})
.catch (error => {
return Promise.reject(error);
});
}
I am getting the following error when I call checkEnrollment in the Role Controller:
"name": "Error",
"message": "Unexpected call outside of task.",
"stack": "Error: Unexpected call outside of task. at Task.query
(\api\node_modules\pg-promise\lib\task.js:118:19)
at Task.obj.oneOrNone (\api\node_modules\pg-promise\lib\database.js:491:31)
at RoleRepository.checkRoleEnrollment....
Any help would be much appreciated. Thanking you in advance.
As per my earlier comment:
That error means you are trying to access connection t allocated by a task somewhere outside of the task's callback function, i.e. the task's callback has returned, the connection was released, and then you are using the connection object allocated by the task from somewhere else, which is, of course, invalid.
b.t.w. I'm the author of pg-promise ;)
Below is what your code effectively doing, in a simplified form:
var cnReference;
db.task(t => {
cnReference = t;
// can only use `t` connection while executing the callback
})
.then(data => {
// we are now outside of the task;
// the task's connection has been closed already,
// and we can do nothing with it anymore!
return cnReference.query('SELECT...');
})
.catch(error => {
// ERROR: Unexpected call outside of task.
// We cannot use a task connection object outside of the task's callback!
});
You need to correct the implementation to make sure this doesn't happen.

Resources