I'm trying to query mySQL database inside Express route, get an array called "subjects", then run axios request with the array. Then I want to display the responses on EJS.
Such as,
// for-each "subject",
var url = `http://news.com/${subject}`
const articles_get =await axios.get(url)
res.render('news',{articles:articles_get.data.articles})
I tried so many things, I keep getting errors about "async/await must be at the top level" and "HEADERS ALREADY SENT". I tried following other examples on SO but none have worked. I think I'm missing some idea. Please any help is appreciated!
Here's my current code that says "SyntaxError: await is only valid in async functions and the top level bodies of modules" I tried to move the function out of the route, then call it inside the route, but it just never returned anything.
app.get("/mySubjects", (req, res) => {
var subjects = [];
var username = req.body["username"];
connection.query(
"SELECT * FROM mySubjects WHERE username = ?",
[username],
async function (error, results, fields) {
if (error) {
throw error;
}
if (results.length > 0) {
results.forEach((result) => {
subjects.push(result.subject);
});
subjects.forEach((subject) => {
//call NEWS API FOR EACH SUBJECT
try {
var url = `https://newsapi.org/v2/top-headlines/sources?category=${subject}apiKey=c7777777777777`;
const news_get = await axios.get(url);
res.render("news", { articles: news_get.data.articles });
} catch (error) {
if (error.response) {
console.log(error);
}
}
});
} else {
res.status(401).send({ message: "Nothing is here." });
}
}
);
});
CodePudding user response:
You can only call res.render()
once as it will render your template and respond to the client. What you need to do is collect all your data first, then render your template.
For example
if (results.length > 0) {
// using Promise.allSettled so it doesn't bail at the first failed request
const allResults = await Promise.allSettled(
results.map(async ({ subject }) => {
const { data: { articles } } = await axios.get("https://newsapi.org/v2/top-headlines/sources", {
params: { // params is the easy way to pass query parameters
category: subject,
apiKey: "c7777777777777",
}
});
return { subject, articles };
})
);
// Extract all the successful responses
const allArticles = allResults
.filter(({ status }) => status === "fulfilled")
.map(({ value }) => value);
// render your template
res.render("news", { allArticles });
// optionally log the failed requests
allResults
.filter(({ status }) => status === "rejected")
.forEach(({ reason }) => {
console.error(reason.response?.data ?? reason.message)
})
} else {
// 401 is for Unauthorized, use 404 for Not Found
res.status(404).send({ message: "Nothing is here." });
}
This will render your news
template, passing a property named allArticles
which is an array of objects with subject
and articles
properties.
CodePudding user response:
You should not send a response inside forEach since it has not done iterating.
The example below will return all response.data
as an array and make sure you handle them accordingly in ejs file.
app.get('/mySubjects', (req, res) => {
const username = req.body['username'];
connection.query(
'SELECT * FROM mySubjects WHERE username = ?',
[username],
function (error, results, fields) {
if (error) throw error;
if (results.length > 0) {
const requests = results.map(({ subject }) =>
axios.get(
`https://newsapi.org/v2/top-headlines/sources?category=${subject}apiKey=c7777777777777`,
),
);
Promise.all(requests)
.then((news) => {
res.render('news', { news });
})
.catch((err) => {
// Handle error
console.log(err.message);
});
} else {
res.status(401).send({ message: 'Nothing is here.' });
}
},
);
});