Node.js: Better way to resolve Promise from another API - node.js

Suppose I have two APIs, the first API is called by the client. Then it will call a API on the storage server to store some data. The storage server will return an response immediately with status code 202.
app.post('/create/:requestId', (req, res) => {
const { requestId } = req.params;
// Call 3rd party API
axios.post('/store', { id: requestId });
});
After the data is successfully stored, the storage service will call another API on the server as a webhook to notify us that the data is stored.:
app.post('/resolve/:requestId', (req, res) => {
// Called by the storage server to notify the data is successfully stored
});
What I need is that, when the client calls POST /create/:requestId, it won't return until the data is successfully stored on the storage server. One way to do so is that:
const pendingRes = [];
app.post('/create/:requestId', async (req, res) => {
const { requestId } = req.params;
// Call 3rd party API
const result = await axios.post('/store', { id: requestId });
if (result.data.success) {
pendingRes.push({ id: requestId, res });
}
});
app.post('/resolve/:requestId', (req, res) => {
// Called by the storage server to notify the data is successfully stored
const { requestId }= req.params;
const resToResolve = pendingRes.find(r => r.id === requestId);
const idx = pendingRes.findIndex(r => r.id === requestId);
if (resToResolve) {
// Response to the create API
resToResolve.res.json({ success: true });
pendingRes.splice(idx, 1);
}
// Response to the resolve API
res.json({ success: true });
});
I know this can solve the problem but I wonder if there is any better way to do that such as using Promise. I don't like this method because actually there are many similar APIs on the storage server and I need to create many arrays for saving pending res.
Of course, I cannot change the API on the storage server :(

You can create and await a Promise which will be resolved at /resolve/:requestId.
const requestId2resolve = new Map();
app.post('/create/:requestId', async (req, res) => {
const { requestId } = req.params;
// Call 3rd party API
const result = await axios.post('/store', { id: requestId });
if (result.data.success) {
await new Promise(resolve => requestId2resolve.set(requestId, resolve));
res.json({ success: true });
}
});
app.post('/resolve/:requestId', (req, res) => {
// Called by the storage server to notify the data is successfully stored
const { requestId }= req.params;
if (requestId2resolve.has(requestId)) {
const resolve = requestId2resolve.get(requestId);
resolve();
requestId2resolve.delete(requestId);
}
// Response to the resolve API
res.json({ success: true });
});

Related

Empty row on MongoDB data insertion using express.js

I want to add data to my MongoDB collection. I'm getting this data via a local Flask API. I'm GETting the data on my React Frontend and it's displaying fine. I'm not sure why I can't do the same thing on my express nodejs backend. I want to get that same data and use it to build the entity that I'm going to store.
This is how I'm attempting to get the data
app.get('/', async (req, res) => {
let initialData = {};
axios.get('http://localhost:3000/details').then((res) => {
initialData = res.data;
});
const recruit = new RecruitModel({ email:initialData.email,
mobile_number:initialData.mobile_number,
name:initialData.name});
try {
await recruit.save()
res.send("inserted data")
} catch (error) {
console.log(error)
}
})
I'm pretty sure something wrong there and nowhere else. Because if I pass static information instead it's correctly stored, no issues.
You are saving to the database's Recruit Collection before the promise is resolved. Since data to save in the Recruit Collection is dependent upon the result from the API which will initially return the promise, therefore, use promise resolving functions to wait for its result.
Solution#1 (using .then function):
app.get('/', async (req, res) => {
let initialData = {};
try {
axios.get('http://localhost:3000/details').then((response) => {
initialData = response.data;
const recruit = new RecruitModel({
email: initialData.email,
mobile_number: initialData.mobile_number,
name: initialData.name,
});
recruit.save().then((response) => res.send('inserted data'));
});
} catch (error) {
console.log(error);
}
});
Solution#2 (using async await keywords):
app.get('/', async (req, res) => {
try {
const response = await axios.get('http://localhost:3000/details');
const recruit = new RecruitModel({
email: response.data.email,
mobile_number: response.data.mobile_number,
name: response.data.name,
});
await recruit.save();
res.send('inserted data');
} catch (error) {
console.log(error);
}
});
Either solution will work in your case.

External API called in a route, and testing the route in node JS

I have the below route created in my application which calls another API for data:
newsr.get('/news', async (req, res) => {
var category = req.params.category;
try {
var url = 'http://newsapi.org/v2/top-headlines?country=in&category=general' + '&apiKey=36f3e29b704f41339af8439dc1228334';
const news_get = await axios.get(url)
res.status(200).json().render('category', { articles: news_get.data.articles })
// return res.status(200).json(news_get)
} catch (error) {
if (error.response) {
console.log(error)
}
}
})
The problem is, when I call the get API http://localhost:8080/news. I do not get the response from the external API. Can you please suggest what to do?

multiple async-await get requests not working in PERN stack app

I'm working on building an inventory management application using PERN stack. I have a modal where I need to make 2 GET requests and when I console.log in front end both requests are getting Status 200 response. However in my express server, first get request is working fine but the second request is not receiving anything.
My frontend code
const openModal = async () => {
setDetailModalOpen(true)
try {
await Promise.all([
(async () => {
const serial_number = props.bacsSerial
const response = await fetch(`http://localhost:5000/bacslist/demoinventory/${serial_number}`)
const parseResponse = await response.json()
console.log(response)
setInputs({
bacsUnit: parseResponse.bacs_unit,
serialNumber: parseResponse.serial_number,
partNumber: parseResponse.part_number,
bacsLocation: parseResponse.bacs_location,
description: parseResponse.bacs_description
})
setBacsId(parseResponse.id)
setBacsData(parseResponse)
})(),
(async () => {
const response2 = await fetch(`http://localhost:5000/bacslist/demoinventory/${bacsId}`)
console.log(response2)
})()
])
} catch (err) {
console.error(err.message)
}
}
My backend code
router.get("/demoinventory/:serial_number", async (req, res) => {
console.log('This one is working')
try {
const {serial_number} = req.params
const getDemoBacs = await pool.query(
"SELECT * FROM demo_inventory WHERE serial_number = $1", [serial_number]
)
res.json(getDemoBacs.rows[0])
} catch (err) {
console.error(err.message)
}
})
router.get("/demoinventory/:bacsId", async (req, res) => {
console.log(req.params)
console.log('This one is not working')
try {
const getHistoryData = await pool.query(
"SELECT * FROM demo_inventory_history"
)
console.log(getHistoryData)
res.json(getHistoryData)
} catch (err) {
console.error(err.message)
}
})
Sorry, Kinda new to this stuff so this isn't exactly an answer but I'm not allowed to leave a comment. I can't see your state variables with the code you posted, but are you sure that BacsId is being set to state before it is used in the second call, or is the parameter in the second call being sent empty, thus not using the right URL? Just a thought.

firebase once('value') function on database not response

I would happy for your help
I'm trying to use firebase on nodejs server.
after initial configurations I build a rest on node server that used to fetch data from firebase database
Here is the first version of the code
app.get('/api/users/:id', (req,res) =>{
const usersRef = firebase.database().ref('users');
usersRef.once('value')
.then((snapshot) => {
console.log(snapshot);
res.status(200).send(snapshot.val())
})
.catch((error) => {
console.log(error);
res.status(404).send(error.message)
})
}))
I've checked the route with postman and found that I didn't received any response from that route
I've check with logs that the route received the request and I found that I must create async call to database, so, I created middleware
And here is the new version of code:
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
};
app.get('/api/users/:id', asyncMiddleware(async (req,res,nex) =>{
const usersRef = firebase.database().ref('users');
await usersRef.once('value')
.then((snapshot) => {
console.log(snapshot);
res.status(200).send(snapshot.val())
})
.catch((error) => {
console.log(error);
res.status(404).send(error.message)
})
}))
But the problem continue even after the changes.
So I found that the reason for the problem is because the 'once' function stack when he tried to fetch data from firebase and not return any response.
I would really like to your help here
thanks
I guess it has to do with the way you handle the asynchronous requests. Try the code below and let's see if we'll get any luck.
const fetchUsers = () => new Promise((resolve, reject) => {
try{
const usersRef = firebase.database().ref('users')
usersRef.once('value', (snapShot) => {
resolve(snapShot)
})
}catch(err){
reject(err)
}
})
app.get('/api/users/:id', async (req,res) =>{
const users = await fetchUsers()
/**
* clean the users response
* and respond with data
*/
res
.status(200)
.json({
data: users //cleaned
})
}))
So, After Investigate I found a solution for this problem.
I've used axios on server send request by using REST API of firebase
https://firebase.google.com/docs/reference/rest/database

Correct way to utilize middleware functions in Express

Hi im creating a proxy endpoint using express with node js, i have it working correctly for one get request, but im wanting to setup multiple get/post request endpoints and wondering if what i have done below is the correct way to carry on to implement the other endpoints, im pretty new with NodeJS and express.
what ive tried is reusing
app.use(validateFirebaseIdToken);
and then app.use(new functionname) before i call the new get Endpoint, i need to reuse the decodedIDToken variable in each of my bigQuery methods, bear in mind there are going to be alot of different bigQuery query methods i will be creating to get data for the end user
var express = require('express')`
var app = express()
const validateFirebaseIdToken = async (req, res, next) => {`
console.log('Check if request is authorized with Firebase ID token');
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&!(req.cookies && req.cookies.__session)) {
console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.',
'Make sure you authorize your request by providing the following HTTP header:',
'Authorization: Bearer <Firebase ID Token>',
'or by passing a "__session" cookie.');
res.status(403).send('Unauthorized');
return;
}
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
console.log('Found "Authorization" header');
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1];
} else {
// No cookie
res.status(403).send('Unauthorized');
return;
}
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
console.log('ID Token correctly decoded', decodedIdToken);
req.user = decodedIdToken;
next();
return;
} catch (error) {
console.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
return;
}
};
const runDailyCategorybigQuery = async (req, res, next) => {
const query = `select count(string_field_3) as Categories, Month(date_field_2) as Month from test.testing_api group by Month`;
const options = {
query: query,
// Location must match that of the dataset(s) referenced in the query.
location: 'US',
useLegacySql: true,
};
// Run the query as a job
const [job] = await bigquery.createQueryJob(options);
console.log(`Job ${job.id} started.`);
console.log(`ID ${req.user.user_id}`);
// Wait for the query to finish
const [rows] = await job.getQueryResults();
res.query = rows;
console.log('Rows:');
rows.forEach(row => console.log(row));
next();
};
const runCategoryMonthsbigQuery = async (req, res, next) => {
const query = `select count(string_field_3) as Categories, Month(date_field_2) as Month from test.testing_api group by Month`;
const options = {
query: query,
// Location must match that of the dataset(s) referenced in the query.
location: 'US',
useLegacySql: true,
};
// Run the query as a job
const [job] = await bigquery.createQueryJob(options);
console.log(`Job ${job.id} started.`);
console.log(`ID ${req.user.user_id}`);
// Wait for the query to finish
const [rows] = await job.getQueryResults();
res.query = rows;
console.log('Rows:');
rows.forEach(row => console.log(row));
next();
};
app.use(validateFirebaseIdToken);
app.use(runDailyCategorybigQuery);
app.get('/getCategories', (req, res) => {
res.json(res.query);
});
//what im thinking of doing
app.use(validateFirebaseIdToken);
app.use(runCategoryMonthsbigQuery);
app.get('/getCategoriesMonth', (req, res) => {
res.json(res.query);
});
What you listed as "what I am thinking of doing" will add the same middleware twice (for validateFirebaseIdToken). This is not necessary as middleware will be called on every request, so you should only add it once.
The second issue is that you are treating your request handlers like middleware. Since each request will be seen by all middleware (as long as they call next) then you will be running both queries and the second one will overwrite the first.
Move your two query middleware functions into request handlers instead. For example:
const runDailyCategorybigQuery = async (req, res) => {
...
res.json(res.query);
}
app.get('/getCategories', runDailyCategorybigQuery);

Resources