Currently I'm working in a project where I'm trying to build a service in express which calls another two external EP. I have a trouble here, because express shows me an error that I can't understand. But I suppose, the way I'm working it should be wrong.
So
app.get("/items/:id", (req, res) => {
return request.get({
url: `https://myapi.com/${req.params.id}`,
json: true
},
(error, response) => {
if(error) {
return res.send("Error ocurred");
}
const itemDesc = request.get({ // Here I'm trying to do the second call and use it later
url: `https://myapi.com/${req.params.id}/description`,
json: true
},
(error, responseDesc) => {
return responseDesc
});
const itemDetails = response.body;
const strPrice = itemDetails.price.toString().split('.');
const numberPrice = parseInt(strPrice[0]);
const floatPrice = strPrice[1] ? parseInt(strPrice[1]) : 00;
return res.send({
id: itemDetails.id,
title: itemDetails.title,
price: {
currency: itemDetails.currency_id,
amount: numberPrice,
decimals: floatPrice,
},
picture: itemDetails.pictures[0].url,
condition: itemDetails.condition,
free_shipping: itemDetails.shipping.free_shipping,
sold_quantity: itemDetails.sold_quantity,
description: itemDesc // Here I'm using the variable of the previous request
});
});
});
Basically, the error I get is that I can't do two calls. I know that because if I remove the nested request, it works.
The error I get is the following:
My question is: Is there any way to do two external request inside the same method?
Thanks in advance
it's cleaner if you do it with async await in your case.
modify your code like this
app.get("/items/:id", async(req, res) => {
try {
const promise1 = fetch(`https://myapi.com/${req.params.id}`).then(data => data.json())
const promise2 = fetch(`https://myapi.com/${req.params.id}/description`)
const [itemDetails, itemDesc] = await Promise.all([promise1, promise2])
const strPrice = itemDetails.price.toString().split('.');
const numberPrice = parseInt(strPrice[0]);
const floatPrice = strPrice[1] ? parseInt(strPrice[1]) : 00;
res.send({
id: itemDetails.id,
title: itemDetails.title,
price: {
currency: itemDetails.currency_id,
amount: numberPrice,
decimals: floatPrice,
},
picture: itemDetails.pictures[0].url,
condition: itemDetails.condition,
free_shipping: itemDetails.shipping.free_shipping,
sold_quantity: itemDetails.sold_quantity,
description: itemDesc // Here I'm using the variable of the previous request
});
} catch (
res.send("Error ocurred")
)
});
Related
I use the Firestore Rest API in nextJs getServersideProps to fetch a firestore doc. It works as expected, but every 5:30min the function getServersideProps gets retriggered without reloading or navigating (is this only on dev environment?) and then the result of the rest api is simply
[ { readTime: '2022-10-28T14:24:01.348248Z' } ]
The document key is missing and no data is present, which breaks the server function (App behaves without showing error).
The fetching function looks like this:
const { GoogleToken } = require('gtoken');
const { documentToJson } = require('./helpers');
const getConfig = require('next/config').default;
const FIRESTORE = getConfig().serverRuntimeConfig.firestore;
export async function fetchWebsitePropsByPath(path: string) {
const body = JSON.stringify({
structuredQuery: {
from: [{ collectionId: 'websites' }],
where: {
compositeFilter: {
op: 'AND',
filters: [
{
fieldFilter: {
field: {
fieldPath: 'path',
},
op: 'ARRAY_CONTAINS',
value: {
stringValue: path,
},
},
},
],
},
},
limit: 1,
},
});
// Authenticate with Google
const gtoken = new GoogleToken({
key: FIRESTORE.key,
email: FIRESTORE.email,
scope: ['https://www.googleapis.com/auth/datastore'], // or space-delimited string of scopes
eagerRefreshThresholdMillis: 5 * 60 * 1000,
});
const getToken = () =>
new Promise((resolve, reject) => {
gtoken.getToken((err, token) => {
if (err) {
reject(err);
}
resolve(token);
});
});
const token: any = await getToken();
let headers = new Headers();
headers.append('Authorization', 'Bearer ' + token.access_token);
const res = await fetch(`${FIRESTORE.api}:runQuery`, {
method: 'POST',
headers,
body: body,
});
const rawData = await res.json();
const id = rawData[0].document.name.split('/').pop();
const docData = documentToJson(rawData[0].document.fields);
docData.id = id;
return docData;
}
I would like to know if I can prevent the refetching every 5:30 min if it is not dev env specific and why the rest api returns nothing here.
so below is my API code which receive two image files one containing only one image and other contains 5 images. then I am using a hosting website called cloudinary to upload them and receive a result which includes the URL to the uploaded image. I am trying to push those result into urlArray so later on I can save them to database. However, the code for database executes before everything else. also when I log urlArray at the end . it is just an empty array. I was wondering what am I doing wrong here?
const upload = multer({
storage: multer.diskStorage({
destination: "./public/uploads",
filename: (req, file, cb) => cb(null, file.originalname),
}),
});
const apiRouter = nc({
onNoMatch(req, res) {
res.statusCode(405), json({ error: `method ${req.method} not allowed` });
},
});
const arrayOfImages = upload.fields([
{ name: "cardImage", maxCount: 1 },
{ name: "images", maxCount: 5 },
]);
apiRouter.post(arrayOfImages, (req, res) => {
const urlArray = [];
let cardImage = "";
const imagesArray = req.files["images"];
cloudinary.uploader.upload(
req.files["cardImage"][0].path,
{ folder: "cardImages" },
async (err, result) => {
if (err) console.log(err);
cardImage = result.secure_url;
console.log(`this is the first call ${cardImage}`);
fs.unlinkSync(req.files["cardImage"][0].path);
}
);
for (let i = 0; i < imagesArray.length; i++) {
cloudinary.uploader.upload(
imagesArray[i].path,
{ folder: "Images" },
async (err, result) => {
if (err) console.log(err);
urlArray.push(result.secure_url);
fs.unlinkSync(req.files["images"][i].path);
// TODO : need to implement the data save to database
}
);
}
dataBaseConnection();
const userItem = new Item({
shortDescription: req.body.shortDescription,
longDescription: req.body.longDescription,
price: req.body.itemPrice,
cardImage: cardImage,
images: urlArray,
});
userItem.save((err) => {
if (err) console.log(err);
return console.log("your prodcut has been added to database.");
});
console.log(urlArray);
console.log(`this is the second call ${cardImage}`);
res.redirect("/dashboard");
});
export default apiRouter;
export const config = {
api: {
bodyParser: false, // Disallow body parsing, consume as stream
},
};
I am not familiar with the coludinary API neither have a run this code, however assuming 'upload' returns a promise ( which it looks like it does) you can just await the results like so:
Note: you may have to inspect the result after await to make sure you are pulling the attribute the right away. I am unfamiliar with the API. But this is the general idea.
Also note that you may have to move you error checking outside to the result object. My example assumes the upload goes through successfully
apiRouter.post(arrayOfImages, async(req, res) => { //<< ---- Notice the async
const urlArray = [];
let cardImage = "";
const imagesArray = req.files["images"];
let result = await cloudinary.uploader.upload( //<<-- NOTE: await here
req.files["cardImage"][0].path,
{ folder: "cardImages" },
async (err, result) => {
if (err) console.log(err);
fs.unlinkSync(req.files["cardImage"][0].path);
}
)
cardImage = result.secure_url; //<<-- pull out the value you need from result after await
for (let i = 0; i < imagesArray.length; i++) {
let res = await cloudinary.uploader.upload( //<<-- NOTE: await here
imagesArray[i].path,
{ folder: "Images" },
async (err, result) => {
if (err) console.log(err);
fs.unlinkSync(req.files["images"][i].path);
}
);
urlArray.push(res.secure_url); //<<-- pull out the value you need from result after await
}
dataBaseConnection();
const userItem = new Item({
shortDescription: req.body.shortDescription,
longDescription: req.body.longDescription,
price: req.body.itemPrice,
cardImage: cardImage,
images: urlArray,
});
userItem.save((err) => {
if (err) console.log(err);
return console.log("your prodcut has been added to database.");
});
console.log(urlArray);
console.log(`this is the second call ${cardImage}`);
res.redirect("/dashboard");
});
So what happened here?
Since the upload function is asynchronous, your code after both the first upload and the loop, execute before the 'upload' actually completes. By using await keyword you can wait for the promise to return.
I'm having a problem right now when i want to remove some code out of my route to put it into a service. I'm just trying to follow the best practices of developing an application.
This is my route right now:
const express = require('express');
const cityRouter = express.Router();
const axios = require('axios');
const NodeCache = require('node-cache');
const myCache = new NodeCache();
cityRouter.get('/:cep', async (request, response) => {
try {
const { cep } = request.params;
const value = myCache.get(cep);
if (value) {
response.status(200).send({
city: value,
message: 'Data from the cache',
});
} else {
const resp = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
myCache.set(cep, resp.data, 600);
response.status(200).send({
city: resp.data,
message: 'Data not from the cache',
});
}
} catch (error) {
return response.status(400);
}
});
module.exports = cityRouter;
I'm using axios to retrieve data from an API, where i have a variable called "cep" as a parameter and then using node-cache to cache it.
And it works with out problems:
enter image description here
But, when i try to put the same code into a service, and then call it into my route:
My service:
const axios = require('axios');
const NodeCache = require('node-cache');
const myCache = new NodeCache();
function verificaCache(cep) {
return async function (request, response, next) {
const value = myCache.get(cep);
console.log(cep);
if (value) {
response.status(200).send({
city: value,
message: 'Data from the cache',
});
} else {
const resp = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
myCache.set(cep, resp.data, 600);
response.status(200).send({
city: resp.data,
message: 'Data not from the cache',
});
}
next();
};
}
module.exports = verificaCache;
My route using the service:
const express = require('express');
const cityRouter = express.Router();
const verificaCache = require('../services/VerificaCacheService');
cityRouter.get('/:cep', async (request, response) => {
const { cep } = request.params;
verificaCache(cep);
response.status(200);
});
module.exports = cityRouter;
By some reason, it doesn't work:
enter image description here
What is the problem that i can't see? I'm a beginner so i'm kinda lost right now.
You have created a high-order function by returning a function in verificaCache(), so to properly call it you need to do it like that await verificaCache(cep)(req, res), remember, the first time you call it, you have a function being returned, since you want the tasks inside of that function to be executed, you need to call it as well.
Take a reading about high-order functions here: https://blog.alexdevero.com/higher-order-functions-javascript/
My recommendation, you could just get rid of the other function you are returning to simplify your code, and let the service only handle business logic, all the http actions should be handled on the controller level:
// Service
function verificaCache(cep) {
const value = myCache.get(cep);
if (value) {
return { city: value, message: 'Data from the cache'})
}
// No need of an else statement because the
// execution will stop at the first return if the condition passes
const resp = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
myCache.set(cep, resp.data, 600);
return { city: resp.data, message: 'Data not from the cache'};
}
// Controller
cityRouter.get('/:cep', async (request, response) => {
const { cep } = request.params;
try {
const data = verificaCache(cep);
// Use json() instead of send()
response.status(200).json(data);
} catch(error) {
// Handle errors here
console.log(error);
}
});
Estamos juntos!
I am using node.js, MySQL, knex, and express.
I am doing a simple query of a database, db.findAllEmoji().
const findAllEmoji = () => {
return knex('emoji')
.select('*');
};
I am working from previous code that works I am modeling after, but am still stuck. There are two large code blocks below The first is from my routes in which I render the page in routes\dashboard.js. The second is what I am modeling after.
What I have below in the first large code block returns undefined unless I use let query = await db.findAllEmoji();, only then will it return the results of the query. That would be fine, but...if I use await, then the .whereRaw and .orderBy throws these errors and I have not been able to get past these. Here's one of them.
TypeError: query.orderBy is not a function at C:\Users\pauli\repos\all-coursework-node-paulwinka\tests\02. MySQL database schema emoji\routes\dashboard.js:21:21 at processTicksAndRejections (internal/process/task_queues.js:97:5)
My sample code did not need await to work, so I would prefer a solution that figures out why my query doesn't work without await..or maybe.
So my questions are, why won't the original query not work without await...and how can I get it to work without await like in my model code?
And if I just must use await in this case, how can I fix the errors with orderBy not working?
I've only been using these for a few weeks and am still learning the ropes. Thanks. :)
const express = require('express');
const db = require('../db');
const debug = require('debug')('app:routes:dashboard');
// creating instance of router.
const router = express.Router();
router.use(express.urlencoded({ extended: false }));
router.use(express.json());
router.get('/', async (req, res, next) => {
try {
const search = req.query.search;
let query = db.findAllEmoji();
if (search) {
query = query.whereRaw('description LIKE ?', ['%' + search + '%']);
} else {
query = query.orderBy('emoji_id');
}
debug(`query length: ${query.length}`);
res.render('dashboard/dashboard-user', {
title: 'Dashboard - Emoji',
active: 'dashboard',
query,
});
} catch (err) {
next(err);
}
});
module.exports = router;
This is the code that did work that I am modeling after...maybe I am missed something obvious.
try {
const industry = req.query.industry;
const search = req.query.search;
const pageSize = parseInt(req.query.pageSize) || 10;
const pageNumber = parseInt(req.query.page) || 1;
const industryOptionList = {
selected: industry || '',
options: [
{ value: '', text: 'All Categories' },
{ value: 'hospitality', text: 'Hospitality' },
{ value: 'foodservice', text: 'Foodservice' },
{ value: 'IT', text: 'IT' },
{ value: 'defense', text: 'Defense' },
{ value: 'finance', text: 'Finance' },
{ value: 'construction', text: 'Construction' },
],
};
let query = db.getAllListings();
if (industry) {
query = query.where('company.industry', industry);
}
if (search) {
// query = query.whereRaw('MATCH (review.title, review.review_text) AGAINST (? IN NATURAL LANGUAGE MODE)', [search]);
query = query.whereRaw(
'listing_title LIKE ? OR company.industry LIKE ? OR company_name LIKE ? OR company_description LIKE ?',
['%' + search + '%', '%' + search + '%', '%' + search + '%', '%' + search + '%']
);
} else {
query = query.orderBy('posted_date');
}
const pager = await pagerUtils.getPager(query, pageSize, pageNumber, req.originalUrl);
const listings = await query.limit(pageSize).offset(pageSize * (pageNumber - 1));
debug(`listings length is ${listings.length}`);
if (!req.xhr) {
res.render('listing/listing-list', {
title: 'Jobify: Listings',
listings,
industry,
industryOptionList,
search,
pager,
});
} else {
res.render('listing/search-results', { listings, pager: pager, layout: null });
}
} catch (err) {
next(err);
}
});
Here is my ..db code too in case it helps.
// get connection config
const config = require('config');
const { sum } = require('lodash');
const databaseConfig = config.get('db');
const debug = require('debug')('app:server');
//connect to the database
const knex = require('knex')({
client: 'mysql',
connection: databaseConfig,
});
const findAllEmoji = () => {
return knex('emoji')
.select('*');
};
module.exports = {
knex,
findAllEmoji,
};
If you are not awaiting or calling .then() for query builder, the query gets only built, but it well never be executed.
Query builder works in a way that you can add more .where, .join etc. parts to the query in different lines of code (like you are doing in router.get('/', async (req, res, next) => {) and when the query is ready it needs to be executed so that it will only then sent to DB sever to get response.
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");
}
}
);
}
);