NodeJS HTTP Requests Promises and Storing Results in Variables - node.js

I'm building a front-end application that makes HTTP requests to 2 separate API's.
http://greetings_api:3000/getGreeting
data {'language': 'es'}
Response: 'Hola'
http://users_api:3000/getUser
data {'userid': 1}
Response: 'Jose Smith'
I have a single route that makes a request to these API's and then returns those responses:
var http = require('http');
var request = require('request-promise');
var greetingOptions = {
uri: 'http://greetings_api:3000/getGreeting',
hostname: 'greetings_api',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'language': 'es'
}
};
var greeting = {
getGreeting: function() {
return request(greetingOptions);
}
}
function myGreeting() {
return greeting.getGreeting();
};
var userOptions = {
uri: 'http://users_api:3000/getUser',
hostname: 'users_api',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'id': 1
}
};
var user = {
getUser: function() {
return request(userOptions);
}
}
function myUser() {
return user.getUser();
};
function getWelcome(req, res) {
// How do you store this....
myUser().then(function(result) {
console.log('result')
return result;
})
// ...and this...
myGreeting().then(function(result) {
console.log('Greet ' + result);
return result;
});
/// ...and then send them with this?
res.send(greeting + ' ' + user);
}
module.exports = { getWelcome };
So with the current code I get the correct output in the console. The problem is that I need to be able to send the response from the route with the combination of both API responses. What is the simplest way to accomplish this?

You are sending the response before the promises resolving.
with async/await we can write asynchronous code that looks and behaves like synchronous.
async function getWelcome(req, res) {
// we can wrap our operation in a try/catch block to handle
// both asynchronous and synchronous errors
try {
// with the await keyword we can wait for all promises to resolve
// before we continue with our code
/* if one of the promises inside Promise.all rejects we move to the catch block */
const [user, greeting] = await Promise.all([
myUser(),
myGreeting()
]);
// send the response if no errors
res.send(greeting + ' ' + user);
catch(e) {
res.status(404).send();
}
}

You need to make 2 parallel requests and send response after completing both. Here Promise.all function can help you.
Promise.all([myUser(), myGreeting()]).then(function(result) {
// result[0] - user
// result[1] - greeting
res.send(result[0] + ' ' + result[1]);
});

Related

How to route to next page after successful payment in MERN app?

I am using Paytm payment gateway for transaction. My front-end is in reactjs and backend is in nodejs and expressjs. I wanted that after successful payment next page is redirected.
Backend Code-
for checking checksum and transaction.
PaytmChecksum.generateSignature(JSON.stringify(paytmParams.body), paytmconfig.merchantkey).then(function(checksum){
paytmParams.head = {
"signature" : checksum
};
var post_data = JSON.stringify(paytmParams);
var options = {
/* for Staging */
hostname: 'securegw-stage.paytm.in',
/* for Production */
// hostname: 'securegw.paytm.in',
port: 443,
path: '/v3/order/status',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': post_data.length
}
};
// Set up the request
var response = "";
var post_req = https.request(options, function(post_res) {
post_res.on('data', function (chunk) {
response += chunk;
});
post_res.on('end', function(){
console.log('Response: ', response);
res.write(response)
});
});
// post the data
post_req.write(post_data);
post_req.end();
});
Front-end code:
will call onPayment function for making the payments
onPayment= async(e)=>{
e.preventDefault();
try {
var amount="1.00";
var mobile_number="+919999999999";
var email="abcd#gmail.com";
var orderId="ORDER_ID"+(new Date().getTime());
let params={
orderId:orderId,
email:email,
amount:amount,
mobile_number:mobile_number
}
var url="http://localhost:4000/payment/paynow";
var request={
url:url,
params:params,
method:"get"
}
const response = await Axios(request);
const processParams=await response.data;
console.log(processParams);
var details={
action : "https://securegw-stage.paytm.in/order/process",
// params : params
params : processParams
}
this.post(details);
} catch (error) {
}
}
You can use react-router-dom
import {useHistory} from 'react-router-dom'
const history = useHistory()
history.push('yourNextPage', {details:detail})
either history.replace should work:
history.replace('yourNextPage', {details:detail})
EDIT
{details:detail} is in case you want to pass your next page a state from the previous page
if you dont want to pass any state
it would be enough
history.push('yourNextPage')
Please refer the code available on the below repository for checksum in node.js.
https://github.com/paytm/Paytm_Node_Checksum
You can also refer the below repository for react (front-end)
https://github.com/paytm/paytm-blink-checkout-react

Awaiting http request in AWS Lambda / Pulumi

I have an AWS Lambda function which triggers https request to Google API. I want the function to be awaitable, so that it does not end immediately, but only after getting response from Google API.
Yes, I know I pay for the execution, but this will not be called often, so it is fine.
The problem is that the http request does not seem to fire correctly. The callback is never executed.
I have made sure that the async/await works as expected by using setTimeout in a Promise. So the issue is somewhere in the https.request.
Also note that I am using Pulumi to deploy to AWS, so there might be some hidden problem in there. I just can't figure out where.
The relevant code:
AWS Lambda which calls the Google API
import config from '../../config';
import { IUserInfo } from '../../interfaces';
const https = require('https');
function sendHttpsRequest(options: any): Promise<any> {
console.log(`sending request to ${options.host}`);
console.log(`Options are ${JSON.stringify(options)}`);
return new Promise(function (resolve, reject) {
console.log(` request to ${options.host} has been sent A`);
let body = new Array<Buffer>();
const request = https.request(options, function (res: any) {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
if (res.statusCode != 200) {
reject(res.statusCode);
}
res.on('data', (data: any) => {
console.log(`body length is ${body.length}`);
console.log('data arrived', data);
body.push(data);
console.log('pushed to array');
console.log(data.toString());
});
});
request.on('end', () => {
console.error('Request ended');
// at this point, `body` has the entire request body stored in it as a string
let result = Buffer.concat(body).toString();
resolve(result);
});
request.on('error', async (err: Error) => {
console.error('Errooooorrrr', err.stack);
console.error('Errooooorrrr request failed');
reject(err);
});
request.end();
console.log(` request to ${options.host} has been sent B`);
});
}
/**
* AWS Lambda to create new Google account in TopMonks domain
*/
export default async function googleLambdaImplementation(userInfo: IUserInfo) {
const payload = JSON.stringify({
"primaryEmail": userInfo.topmonksEmail,
"name": {
"givenName": userInfo.firstName,
"familyName": userInfo.lastName
},
"password": config.defaultPassword,
"changePasswordAtNextLogin": true
});
const resultResponse: Response = {
statusCode: 200,
body: 'Default response. This should not come back to users'
}
console.log('Calling google api via post request');
try {
const options = {
host: 'www.googleapis.com',
path: '/admin/directory/v1/users',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': payload.length.toString()
},
form: payload
}
const responseFromGoogle = await sendHttpsRequest(options);
console.log('responseFromGoogle', JSON.stringify(responseFromGoogle));
}
catch (err) {
console.log('Calling google api failed with error', err);
resultResponse.statusCode = 503;
resultResponse.body = `Error creating new Google Account for ${userInfo.topmonksEmail}.`;
return resultResponse;
}
console.log('request to google sent');
return resultResponse;
}
The problem is that the http request does not seem to fire correctly. The callback is never executed.
I believe this part of the issue is related to some combination of (a) potentially not actually sending the https request and (b) not using the correct callback signature for https.request. See the documentation at https://nodejs.org/api/https.html#https_https_request_options_callback for details on both of these.
Use node-fetch package
The following example works for me using node-fetch:
import * as aws from "#pulumi/aws";
import fetch from "node-fetch";
const api = new aws.apigateway.x.API("api", {
routes: [{
method: "GET", path: "/", eventHandler: async (ev) => {
const resp = await fetch("https://www.google.com");
const body = await resp.text();
return {
statusCode: resp.status,
body: body,
}
},
}],
})
export const url = api.url;
Pulumi complains, it something like "Can not serialize native function" or something like that. The problematic part is that node-fetch relies on Symbol.iterator
As noted in the comments, some of the conditions that can lead to this are documented at https://pulumi.io/reference/serializing-functions.html. However, I don't see any clear reason why this code would hit any of those limitations. There may be details of how this is used outside the context of the snippet shared above which lead to this.

How to make a form-data request with koa?

I am trying to replicate a login form's behaviour through koa.
The login form does:
<form id="loginForm" method="post" action="http://myaddress:3000/auth" enctype="multipart/form-data">
I'm using the koa request and form-data modules:
var form = new FormData();
form.append('identification', 'userId');
form.append('password', 'userPassword');
var options = {
url: DB_SERVER_URL + 'auth',
method: 'POST',
formData: form
};
var response = yield request(options);
console.log('response.statusCode: ' + response.statusCode);
But I always get a 400 response.
I've tried just using form.submit(DB_SERVER_URL + 'auth', function(err, res) { ... } which works, but I like koa's yield functionality and ideally I want to avoid having to deal with callbacks.
Any ideas?
Koa accepts multiple yield inputs that can be obtained from your current code more or less easily depending on your current setup:
a promise. As form-data doesn't seem to use them, we'll create one with Q
var Q = require('q');
var promise = Q.ninvoke(form, "submit", DB_SERVER_URL + 'auth');
var response = yield promise;
console.log('response.statusCode: ' + response.statusCode);
or a thunk, a wrapper function as you used in your answer, but there are libraries that can handle the wrapping for you (here, thunkify-wrap):
var thunkify = require('thunkify-wrap');
var submit = thunkify(form.submit, form); // the context is needed in this case
var response = yield submit(DB_SERVER_URL + 'auth');
console.log('response.statusCode: ' + response.statusCode);
I ended up using form.submit(DB_SERVER_URL + 'auth', function(err, res) { ... }, but wrapped the callbacks so I could use yield to maintain a synchronous control flow.
Here's my wrapper for the callback to the form.submit to receive the response:
function makeLoginRequest(formData) {
var form = new FormData();
form.append('identification', formData.identification);
form.append('password', formData.password);
var DB_SERVER_URL = 'http://myurl:3000/';
return function(callback) {
form.submit(DB_SERVER_URL + 'auth', function(error, response) {
callback(error, response);
});
}
}
And here's my wrapper for the callback for receiving the response body:
function getLoginResponseData(response) {
return function(callback) {
response.on("data", function(chunk) {
callback(null, chunk);
});
}
}
This lets me use yield to maintain a synchronous control flow:
var response = yield makeLoginRequest(this.request.body);
console.log('response.statusCode: ' + response.statusCode);
var chunk = yield getLoginResponseData(response);
console.log("BODY: " + chunk);
I'm a node and koa beginner, so if you have a better way please let me know!
If you are using koa-request I was able to do this.
const request = require('koa-request');
const response = yield request({
method: 'POST',
url: 'https://whatsever.com',
form: {
itema: 'vala',
itemb: 'valb',
},
headers: {
'Content-type': 'application/x-www-form-urlencoded'
}
});
this.body = response.body;
If you need multipart look here: https://www.npmjs.com/package/request#multipartform-data-multipart-form-uploads.
Remember that koa-request wraps therequest module

Using Q promises in HTTP requests with NodeJs

I'm trying to make a chain of promises functions which use HTTP requests in NodeJS with Kraken framework.
My code could work in 90% of cases, but if the distant requested server takes time to respond, the code will return an error with undefined values. So I think Q is a good solution to prevent that.
Here's the situation :
We access to a URL with a "code" parameter -> the route controller takes this param to use it in a HTTP POST request -> the response (a token) is stored in a variable and used in an other HTTP GET request -> the response (multiple JSON objects) is stored in variable too -> all variables are stored in a MongoDB.
If functions are not used in this order, of course it fails.
var Q = require('q');
module.exports = function (router) {
router.get('/', function (req, res) {
var codein = req.param('code');
if(codein){
console.log('Provided code: ' + codein+'\n');
getAccessToken(codein).then(function(token){
console.log('Provided AccessToken: ' + token + '\n');
getUsername(token).then(function(userdata){
console.log('Provided Username: ' + JSON.parse(userdata).username + '\n');
storeData(userdata).then(function(msg){
console.log(msg);
res.redirect('/dashboard/' + JSON.parse(userdata).username);
});
});
});
}
else{
console.log('Access Denied, redirecting...');
res.redirect('/');
}
});
};
This method works, but actually didn't resolve the problem, because sometimes variable are undefined again. I think it's my request functions which aren't well made...
Here's an example of the first function with POST request :
var getAccessToken = function(cod){
var def = Q.defer();
var data = querystring.stringify({
client_id:"1234567890",
client_secret:"******",
grant_type:"authorization_code",
redirect_uri:"http://localhost:8000/r/callback",
code:cod
});
var options = {
host: 'domain.server.com',
port: 443,
path: '/api/oauth2/token',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(data)
}
};
var response = "";
var req = https.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
response += chunk;
});
res.on('end', function(){
var json = JSON.parse(response);
var acto = json.access_token;
def.resolve(acto);
});
});
req.write(data);
req.end();
return def.promise;
};
In this case the acto variable can be undefined... So am I using Q in a wrong way ?
EDIT
To understand my problem, let me show you what can I have in my output console (really rare but happens) :
Provided code: 12345678910
Provided Username: user543210
Instead of :
Provided code: 12345678910
Provided AccessToken: 9876543210
Provided Username: user
I think you need to account for 2 scenarios
Where the Twitch API takes time to respond.
The Twitch response cannot be parsed
The code
res.on('end', function(){
var json = JSON.parse(response);
var acto = json.access_token;
def.resolve(acto);
});
Should be modified as:
try {
var json = JSON.parse(response);
var acto = json.access_token;
//check if acto is undefined
if (acto === undefined) {
def.reject('Some error message');
} else {
def.resolve(acto);
}
} catch (error) {
//since the JSON could not be parse
def.reject(error);
}

Podio API addItem call

I'm trying to implement https://developers.podio.com/doc/items/add-new-item-22362 Podio API addItem call in a nodejs module. Here is the code:
var _makeRequest = function(type, url, params, cb) {
var headers = {};
if(_isAuthenticated) {
headers.Authorization = 'OAuth2 ' + _access_token ;
}
console.log(url,params);
_request({method: type, url: url, json: true, form: params, headers: headers},function (error, response, body) {
if(!error && response.statusCode == 200) {
cb.call(this,body);
} else {
console.log('Error occured while launching a request to Podio: ' + error + '; body: ' + JSON.stringify (body));
}
});
}
exports.addItem = function(app_id, field_values, cb) {
_makeRequest('POST', _baseUrl + "/item/app/" + app_id + '/',{fields: {'title': 'fgdsfgdsf'}},function(response) {
cb.call(this,response);
});
It returns the following error:
{"error_propagate":false,"error_parameters":{},"error_detail":null,"error_description":"No matching operation could be found. No body was given.","error":"not_found"}
Only "title" attribute is required in the app - I checked that in Podio GUI. I also tried to remove trailing slash from the url where I post to, then a similar error occurs, but with the URL not found message in the error description.
I'm going to setup a proxy to catch a raw request, but maybe someone just sees the error in the code?
Any help is appreciated.
Nevermind on this, I found a solution. The thing is that addItem call was my first "real"-API method implementation with JSON parameters in the body. The former calls were authentication and getApp which is GET and doesn't have any parameters.
The problem is that Podio supports POST key-value pairs for authentication, but doesn't support this for all the calls, and I was trying to utilize single _makeRequest() method for all the calls, both auth and real-API ones.
Looks like I need to implement one for auth and one for all API calls.
Anyway, if someone needs a working proof of concept for addItem call on node, here it is (assuming you've got an auth token beforehand)
_request({method: 'POST', url: "https://api.podio.com/item/app/" + app_id + '/', headers: headers, body: JSON.stringify({fields: {'title': 'gdfgdsfgds'}})},function(error, response, body) {
console.log(body);
});
You should set content-type to application/json
send the body as stringfied json.
const getHeaders = async () => {
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json; charset=utf-8',
};
const token = "YOUR APP TOKEN HERE";
headers.Authorization = `Bearer ${token}`;
return headers;
}
const createItem = async (data) => {
const uri = `https://api.podio.com/item/app/${APP_ID}/`;
const payload = {
fields: {
[data.FIELD_ID]: [data.FIELD_VALUE],
},
};
const response = await fetch(uri, {
method: 'POST',
headers: await getHeaders(),
body: JSON.stringify(payload),
});
const newItem = await response.json();
return newItem;
}

Resources