I'm learning to build APIs with Express. I figured that every time I have to send a successful JSON response, I have to duplicate most of the code. So I tried to put it in a function like so in one of my controller modules:
const successResponse = (res, statusCode, obj, coll = false) => {
const successObj = { status: "success" };
if (coll) successObj.results = obj.length;
successObj.data = { obj };
return res.status(statusCode).json(successObj);
};
exports.getPlayer = asyncErrorHandler(async (req, res, next) => {
const player = await Player.findById(req.params.id);
if (!player) return next(new AppError("Player not found", 404));
successResponse(res, 200, player);
}
The problem is in the line successObj.data = { obj } where I want the result to be the name of the argument that is passed as key and the object as value(s). The result I currently get is:
{
"status": "success",
"data": {
"obj": { // I want this key to be "player" here
"name": "Sal"
}
}
}
As the comment says I would like the key to be the string that I passed as the object name. I can do it by passing another argument as a string but it will always be the same as the object name that I pass. Is there a better way to do this?
You pass the name to the successResponse like this:
exports.getPlayer = asyncErrorHandler(async (req, res, next) => {
const player = await Player.findById(req.params.id);
if (!player) return next(new AppError("Player not found", 404));
successResponse(res, 200, {objName: "player" , value: player);
}
And then change your successResponse like this:
// Set a suitable default value for objName (e.g. "obj") if you don't want to send it every time
const successResponse = (res, statusCode, {objName = "obj", value}, coll = false) => {
const successObj = { status: "success" };
if (coll) successObj.results = obj.length;
successObj.data = { [objName]: {value}}; // { player : { name : "Sal"} }
return res.status(statusCode).json(successObj);
};
EDIT 1 => NOTE: You can't access the name of the variable that passed to a function unless the name string was explicitly passed. Because only the value that a variable held is passed along not the name. This why you have to pass it the name. The solution I gave would not need another argument to function but it will need another key/value inside the object passed.
**EDIT 2 => ** Even more cleaner solution
exports.getPlayer = asyncErrorHandler(async (req, res, next) => {
const player = await Player.findById(req.params.id);
if (!player) return next(new AppError("Player not found", 404));
successResponse(res, 200, {player}); //Same as {"player" : player}
}
And then change your successResponse like this:
const successResponse = (res, statusCode, obj, coll = false) => {
const successObj = { status: "success" };
if (coll) successObj.results = obj.length;
successObj.data = { ...obj };
return res.status(statusCode).json(successObj);
};
You are getting a single object and trying to use that as key and value. You can try something like this:
Suppose your obj is
var obj = { player: {
"name": "Sal"
}}
then while pushing to data you can follow these steps:
var selectedKey = Object.keys(obj)[0]; // assuming you will have a single key, if you have multiple then select based on the index
var selectedKeyData = Object.values(obj)[0];
var data = {};
data[selectedKey] = selectedKeyData;
Now, your data will have desired result.
Related
I tried to getting result using mssql database using mssql npm and push it to an array. It seems that response will return an empty array outside of the map function, your help will be much appreciated, thank you.
module.exports = {
someRouteHandler: async function(req, res, next) {
const fileStream = await readFileFromS3(req.body.filename); //a function to read file from AWS S3
if (req.body.productName === "Nike" && type === "Male") {
const result = await getBrandInformation(req.body, fileStream); //this function will parse data from the file and return object result
const { brandInformation, brandItems } = result;
const noneDuplicateArrayContainer = [];
const duplicateArrayContainer = [];
for ( const { itemNumber } of brandItems ) {
let items = await getMatchingList(itemNumber); // will query to database if itemNumber has duplicate or none
if (items.length > 1) {
items.map(async({ identifier }) => {
//if identifier not null query cost
if (identifier) {
let cost = await queryCostToDb(identifier); //will query cost from database
duplicateArrayContainer.push({
brandItems, identifier, cost
})
//if identifier is null no cost to save
} else {
duplicateArrayContainer.push({
brandItems, identifier
})
}
});
//if items length is not greater than 1 meaning no duplicate
} else {
items.map(({ identifier }) => {
let cost = await queryCostToDb(identifier); //will query cost from database
noneDuplicateArrayContainer.push({
brandItems, identifier, cost
});
})
}
}
// when sending response noneDuplicateArrayContainer and duplicateArrayContainer is [ ]
// in the console, it has data, but response is delay
return res.status(200).json({ brandInformation, noneDuplicateArrayContainer, duplicateArrayContainer })
}
}
}
I already fixed the issue, implemented await Promise.all in my code, thanks all for the help
I'm running a Parse Query on my User class.
I want to retrieve users that are contained in an array of strings (ID).
const usersQuery = new Parse.Query(User).containedIn('objectId', customersArrayId).find({ useMasterKey: true });
Which works, actually. I'm getting a [ParseUser { _objCount: 6, className: '_User', id: 'iYIJ7Zrmms' }], because only 1 user matches.
But well, my issue is that I'm only getting ParseUser { _objCount: 6, className: '_User', id: 'iYIJ7Zrmms' }. This class contains other fields (firstname, lastname, e.g.) that are not returned.
When I performed the same thing, looping on my customersArrayId and performing .get():
const customerId = customersArrayId[index];
promises.push(new Parse.Query(User).get(customerId).then((user) => {
return user.toJSON();
}, (error) => console.error(error)));
I'm getting the full object, as expected. But it doesn't seem to be the right way of querying Parse objects from an array of ids.
I can't find anything in the docs about it, any idea why containedIn only returns a part of the queried objects?
I actually get the difference:
new Parse.Query(User).get(customerId)
=> returns the Parse Object
new Parse.Query(User).containedIn('objectId', customersArrayId)
=> returns the Parse User, subclass of a Parse Object.
And well, then, this thread was useful: Get user from parse in Javascript
I ended up using :
usersQuery.then(customersResponse => {
const customers = [];
for (let index = 0; index < customersResponse.length; index++) {
const customer = {
...customersResponse[index].toJSON(),
...customersResponse[index].attributes
};
...
Still not sure that the best answer.
EDIT
router.get('/:userId/shop/customers/details', checkUserMatch, (req, res, next) => {
if('shops' in req.jwtData.data) {
const shopId = req.jwtData.data.shops[0];
const query = new Parse.Query('OtherStuff');
const Shop = Parse.Object.extend('Shops');
query.equalTo('shop', new Shop({id: shopId})).find().then((otherStuffs) => {
const customersArrayId = otherStuffs.map(otherStuff => otherStuff.toJSON().user.objectId);
const usersQuery = new Parse.Query('_User').containedIn('objectId', customersArrayId).find({ useMasterKey: true });
usersQuery.then(customersResponse => {
const customers = [];
for (let index = 0; index < customersResponse.length; index++) {
let customer = {
...customersResponse[index].toJSON(),
...customersResponse[index].attributes
};
const customerId = customer.objectId;
const stuffQuery = new Parse.Query('Stuff').equalTo('user', new UserModel({objectId: customerId})).find().then((stuff) => {
return stuff;
});
const otherStuffQuery = new Parse.Query('otherStuff').equalTo('user', new UserModel({objectId: customerId})).find().then((otherStuff) => {
return otherStuff;
});
Promise.all([stuffQuery, otherStuffQuery]).then((data) => {
const stuff = data[0];
const otherStuff = data[1];
customer = {
...customer,
stuff,
otherStuff,
}
customers.push(customer);
if(index === customersResponse.length - 1) { // last customer
res.json({
success: true,
data: customers
});
}
})
}
});
});
}
});
I just started out with Node.js and AWS DynamoDB and I'm stuck with a very basic problem I believe. I'm looking for a way to return a boolean if a particular key exists in a table. So here's the code I have so far:
const AWS = require('aws-sdk')
const TOKEN_TABLE = process.env.TOKENS_TABLE
const dynamoDb = new AWS.DynamoDB.DocumentClient()
module.exports = {
isValid: function (token) {
const params = {
TableName: TOKEN_TABLE,
Key:
{
token: token
}
}
var exists = false
dynamoDb.get(params, (error, result) => {
if (result.Item)
exists = true
else
exists = false
})
return (exists)
}
}
When i call this function, the value of 'exists' never changes after it was declared even if the item I'm looking for is in the table. I've looked at similar questions and none of them could really help me out or a least explain why this occurs. Thanks
First, dynamoDb.get returns a promise. Therefore, you return 'exists' before your promise finishes and returns. What I've found to be the best way and cleanest way around this is to make your function async and await the return of the promise.
For example,
const AWS = require('aws-sdk')
const TOKEN_TABLE = process.env.TOKENS_TABLE
const dynamoDb = new AWS.DynamoDB.DocumentClient()
module.exports = {
isValid: async function (token) {
const params = {
TableName: TOKEN_TABLE,
Key:
{
token: token
},
AttributesToGet: [
'token'
]
}
var exists = false
let result = await dynamoDb.get(params).promise();
if (result.Item !== undefined && result.Item !== null) {
exists = true
}
return (exists)
}
}
I'm using an HTTP-triggered Firebase cloud function to make an HTTP request. I get back an array of results (events from Meetup.com), and I push each result to the Firebase realtime database. But for each result, I also need to make another HTTP request for one additional piece of information (the category of the group hosting the event) to fold into the data I'm pushing to the database for that event. Those nested requests cause the cloud function to crash with an error that I can't make sense of.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const request = require('request');
exports.foo = functions.https.onRequest(
(req, res) => {
var ref = admin.database().ref("/foo");
var options = {
url: "https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=****",
json: true
};
return request(
options,
(error, response, body) => {
if (error) {
console.log(JSON.stringify(error));
return res.status(500).end();
}
if ("results" in body) {
for (var i = 0; i < body.results.length; i++) {
var result = body.results[i];
if ("name" in result &&
"description" in result &&
"group" in result &&
"urlname" in result.group
) {
var groupOptions = {
url: "https://api.meetup.com/" + result.group.urlname + "?sign=true&photo-host=public&key=****",
json: true
};
var categoryResult = request(
groupOptions,
(groupError, groupResponse, groupBody) => {
if (groupError) {
console.log(JSON.stringify(error));
return null;
}
if ("category" in groupBody &&
"name" in groupBody.category
) {
return groupBody.category.name;
}
return null;
}
);
if (categoryResult) {
var event = {
name: result.name,
description: result.description,
category: categoryResult
};
ref.push(event);
}
}
}
return res.status(200).send("processed events");
} else {
return res.status(500).end();
}
}
);
}
);
The function crashes, log says:
Error: Reference.push failed: first argument contains a function in property 'foo.category.domain._events.error' with contents = function (err) {
if (functionExecutionFinished) {
logDebug('Ignoring exception from a finished function');
} else {
functionExecutionFinished = true;
logAndSendError(err, res);
}
}
at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1436:15)
at /user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1479:13
at Object.forEach (/user_code/node_modules/firebase-admin/node_modules/#firebase/util/dist/index.node.cjs.js:837:13)
at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1462:14)
at /user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1479:13
at Object.forEach (/user_code/node_modules/firebase-admin/node_modules/#firebase/util/dist/index.node.cjs.js:837:13)
at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1462:14)
at /user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1479:13
at Object.forEach (/user_code/node_modules/firebase-admin/node_modules/#firebase/util/dist/index.node.cjs.js:837:13)
at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1462:14)
If I leave out the bit for getting the group category, the rest of the code works fine (just writing the name and description for each event to the database, no nested requests). So what's the right way to do this?
I suspect this issue is due to the callbacks. When you use firebase functions, the exported function should wait on everything to execute or return a promise that resolves once everything completes executing. In this case, the exported function will return before the rest of the execution completes.
Here's a start of something more promise based -
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const request = require("request-promise-native");
exports.foo = functions.https.onRequest(async (req, res) => {
const ref = admin.database().ref("/foo");
try {
const reqEventOptions = {
url:
"https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=xxxxxx",
json: true
};
const bodyEventRequest = await request(reqEventOptions);
if (!bodyEventRequest.results) {
return res.status(200).end();
}
await Promise.all(
bodyEventRequest.results.map(async result => {
if (
result.name &&
result.description &&
result.group &&
result.group.urlname
) {
const event = {
name: result.name,
description: result.description
};
// get group information
const groupOptions = {
url:
"https://api.meetup.com/" +
result.group.urlname +
"?sign=true&photo-host=public&key=xxxxxx",
json: true
};
const categoryResultResponse = await request(groupOptions);
if (
categoryResultResponse.category &&
categoryResultResponse.category.name
) {
event.category = categoryResultResponse.category.name;
}
// save to the databse
return ref.push(event);
}
})
);
return res.status(200).send("processed events");
} catch (error) {
console.error(error.message);
}
});
A quick overview of the changes -
Use await and async calls to wait for things to complete vs. being triggered in a callback (async and await are generally much easier to read than promises with .then functions as the execution order is the order of the code)
Used request-promise-native which supports promises / await (i.e. the await means wait until the promise returns so we need something that returns a promise)
Used const and let vs. var for variables; this improves the scope of variables
Instead of doing checks like if(is good) { do good things } use a if(isbad) { return some error} do good thin. This makes the code easier to read and prevents lots of nested ifs where you don't know where they end
Use a Promise.all() so retrieving the categories for each event is done in parallel
There are two main changes you should implement in your code:
Since request does not return a promise you need to use an interface wrapper for request, like request-promise in order to correctly chain the different asynchronous events (See Doug's comment to your question)
Since you will then call several times (in parallel) the different endpoints with request-promise you need to use Promise.all() in order to wait all the promises resolve before sending back the response. This is also the case for the different calls to the Firebase push() method.
Therefore, modifying your code along the following lines should work.
I let you modifying it in such a way you get the values of name and description used to construct the event object. The order of the items in the results array is exactly the same than the one of the promises one. So you should be able, knowing that, to get the values of name and description within results.forEach(groupBody => {}) e.g. by saving these values in a global array.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
var rp = require('request-promise');
exports.foo = functions.https.onRequest((req, res) => {
var ref = admin.database().ref('/foo');
var options = {
url:
'https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=****',
json: true
};
rp(options)
.then(body => {
if ('results' in body) {
const promises = [];
for (var i = 0; i < body.results.length; i++) {
var result = body.results[i];
if (
'name' in result &&
'description' in result &&
'group' in result &&
'urlname' in result.group
) {
var groupOptions = {
url:
'https://api.meetup.com/' +
result.group.urlname +
'?sign=true&photo-host=public&key=****',
json: true
};
promises.push(rp(groupOptions));
}
}
return Promise.all(promises);
} else {
throw new Error('err xxxx');
}
})
.then(results => {
const promises = [];
results.forEach(groupBody => {
if ('category' in groupBody && 'name' in groupBody.category) {
var event = {
name: '....',
description: '...',
category: groupBody.category.name
};
promises.push(ref.push(event));
} else {
throw new Error('err xxxx');
}
});
return Promise.all(promises);
})
.then(() => {
res.send('processed events');
})
.catch(error => {
res.status(500).send(error);
});
});
I made some changes and got it working with Node 8. I added this to my package.json:
"engines": {
"node": "8"
}
And this is what the code looks like now, based on R. Wright's answer and some Firebase cloud function sample code.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const request = require("request-promise-native");
exports.foo = functions.https.onRequest(
async (req, res) => {
var ref = admin.database().ref("/foo");
var options = {
url: "https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=****",
json: true
};
await request(
options,
async (error, response, body) => {
if (error) {
console.error(JSON.stringify(error));
res.status(500).end();
} else if ("results" in body) {
for (var i = 0; i < body.results.length; i++) {
var result = body.results[i];
if ("name" in result &&
"description" in result &&
"group" in result &&
"urlname" in result.group
) {
var groupOptions = {
url: "https://api.meetup.com/" + result.group.urlname + "?sign=true&photo-host=public&key=****",
json: true
};
var groupBody = await request(groupOptions);
if ("category" in groupBody && "name" in groupBody.category) {
var event = {
name: result.name,
description: result.description,
category: groupBody.category.name
};
await ref.push(event);
}
}
}
res.status(200).send("processed events");
}
}
);
}
);
Now I have this code to create sitemap.xml when I run /sitemap.xml
database = firebase.database();
var ref = database.ref('urls');
ref.on('value', gotData, errData);
function errData(err){
console.log('Error!');
console.log(err);
}
function gotData(data){
result = data.val()
return Object.keys(result)
.filter(key => result[key].last_res > 5)
.map(key => ({url: '/' + result[key].url_site + '/'}));
var urls = gotData(data);
}
when i try running console.log(urls) in gotData(data) function, it returns as
{ salmon:
{ count: 1,
last_res: 10,
url_site: 'salmon' },
'salmon-food':
{ count: 1,
last_res: 601,
url_site: 'salmon-food' } }
I need to return 'urls' in gotData(data) to create sitemap.xml.
var sitemap = sm.createSitemap({
hostname: 'xxx.com',
cacheTime: 600000,
urls: urls
});
app.get('/sitemap.xml', function(req, res) {
sitemap.toXML( function (err, xml) {
if (err) {
return res.status(500).end();
}
res.header('Content-Type', 'application/xml');
res.send( xml );
});
});
}
But now its error on var = sitemap as
urls: urls -> urls is not defined
So how can I fix it?
You are assigning and returning in one line. You either need to define the variable and then return it, or just return the result directly without assigning to a variable.
return Object.keys(result)
.filter(key => result[key].last_res > 5)
.map(key => ({url: '/' + result[key].url_site + '/'}));
It's also not clear where you are defining the urls variable that you are passing in the sm.createSitemap() function. Make sure that it exists within the scope that you are defining var sitemap.
You are likely missing a line similar to the following:
var urls = gotData(data);