First Node/Express app.
I'm having a hard time wrapping my head around on how to retrieve data from an endpoint and rendering it in the browser.
I have a dataservice.js that gets a JSON object from an endpoint like this:
const http = require('http');
getFinhockeyData = function() {
http.get('http://tilastopalvelu.fi/ih/modules/mod_standings/helper/standings.php?statgroupid=3545', (res) => {
console.log(`Got response: ${res.statusCode}`);
var body = "";
res.on('data', function (chunk) {
body += chunk;
})
res.on('end', function () {
var data = JSON.parse(body);
console.log('data parsed.');
console.log('first team name: ' + data.teams[0].TeamName);
console.log(typeof data);
return data;
})
}).on('error', (e) => {
console.log(`Got error from Finhockey: ${e.message}`);
});
}
module.exports.getFinhockeyData = getFinhockeyData;
Up until now things work and the data object can be console.logged and its content is usable.
The router.js looks currently like this:
'use strict';
const express = require('express');
const async = require('async');
const router = express.Router();
const dataservice = require('./dataservice.js')
router.get('/', function(req, res) {
async.series([
function(callback) {
getFinhockeyData(callback)
}
],
function(err, results) {
console.log('start rendering');
res.render('index', { data: data });
})
});
module.exports = router;
When I run the app and refresh the / route, I can see from the console that the getFinhockeyData is called and the data object's content is available in dataservice.js's console.logs, but the browser window hangs and the res.render part is never reached.
I understand that the rendering should be done only after the endpoint data request has finished (async.series usage), but it seems that I lack a fundamental understanding on how to actually use the result data from the getFinhockeyData function in the main route.
Any advice on this? I'll be happy to provide more info if necessary.
Firstly, doing the request is asynchronous, so you'll have to use either a callback or a promise.
Even the async middleware won't let you just return data from an asynchronous call, it requires a callback, but using native promises seems easier here
const http = require('http');
getFinhockeyData = function() {
return new Promise( (resolve, reject) => {
http.get('http://tilastopalvelu.fi/ih/modules/mod_standings/helper/standings.php?statgroupid=3545', (res) => {
var body = "";
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
resolve( JSON.parse(body) );
});
}).on('error', reject);
});
}
module.exports.getFinhockeyData = getFinhockeyData;
Also note that you're exporting as a module with a property
module.exports.getFinhockeyData = getFinhockeyData;
when you're going to use that in the routes, you have to use the property
const dataservice = require('./dataservice.js');
router.get('/', function(req, res) {
dataservice.getFinhockeyData().then(function(data) {
res.render('index', { data: JSON.stringify(data) });
}).catch(function(err) {
// epic fail, handle error here
});
});
You are responding to your route call with
res.render('index', { data: data });
But there is no data variable. It should be
res.render('index', { data: results });
Which is the variable where you are storing your data when it comes from the callback
The reason for res.render() not being called is, http requests are async. To get the response a callback must be passed, which you did but forgot to call it in the dataservice.js
This should help...
Change your dataservice.js like the following...
const http = require('http');
getFinhockeyData = function(callback) {
http.get('http://tilastopalvelu.fi/ih/modules/mod_standings/helper/standings.php?statgroupid=3545', (res) => {
console.log(`Got response: ${res.statusCode}`);
var body = "";
res.on('data', function (chunk) {
body += chunk;
})
res.on('end', function () {
var data = JSON.parse(body);
console.log('data parsed.');
console.log('first team name: ' + data.teams[0].TeamName);
console.log(typeof data);
callback(null, data); //returning the data to the callback
})
}).on('error', (e) => {
console.log(`Got error from Finhockey: ${e.message}`);
callback(e, null);
});
}
module.exports.getFinhockeyData = getFinhockeyData;
Change your router.js like the following...
router.get('/', function(req, res) {
async.series([
function(callback) {
getFinhockeyData(callback)
}
],
function(err, results) {
if(err === null){
console.log('start rendering');
res.render('index', { data: results[0] });
}
})
});
Related
I'm getting error cannot set headers on express js, I think the problem is have to write setHeader, i was set but stil can't, this my code:
router.get('/cek', (req, res) => {
const child = execFile(commandd, ['-c', 'config', 'GSM.Radio.C0']);
child.stdout.on('data',
function (data) {
value = (JSON.stringify(data));
x = value.split('.');
y = JSON.stringify(x[2])
result = y.replace(/\D/g, "");
res.setHeader('Content-Type', 'text/html');
res.send(result);
}
);
child.stderr.on('data',
function (data) {
console.log('err data: ' + data);
}
);
});
I tired to fixing this error for two days, but still cannot, anybody can help?
As stated by Frederico Ibba, this is usually caused after res.send is sent and there is still data being processed... Your workaround for this may simply be to receive all the data before sending it out using res.send. You can try this.
async function executeCommand() {
return new Promise((resolve, reject) => {
const child = execFile(commandd, ['-c', 'config', 'GSM.Radio.C0']);
child.stdout.on('data',
function (data) {
value = (JSON.stringify(data));
x = value.split('.');
y = JSON.stringify(x[2])
result = y.replace(/\D/g, "");
resolve(result);
}
);
child.stderr.on('data',
function (err) { // Renamed data for err for clarification
reject(err);
}
);
});
}
router.get('/url', async (req, res) => {
try {
const result = await executeCommand();
res.setHeader('Content-Type', 'text/html');
res.send(result);
} catch(error) {
// There was an error. I'm throwing a 500
res.sendStatus(500);
}
});
Note that this will be effective only if you are confident that the data is being fired once, as indicated by skirtle
I am trying to add some additional values to each item of an array. So I have an array with objects and they have: x, y and z fields. I then want to add additional items to each object in the array based on a http.get call's response.
Main array is: posts
See code below:
router.get('/api/posts', function(req, res){
postModel.find({})
.limit(10)
.exec(function(err, posts) {
var options = {
host: 'localhost',
port: 3000,
path: '/user?id=12345678',
method: 'GET'
};
if(posts){
posts.forEach(function(post) {
var req = http.get(options, function(res) {
var bodyChunks = [];
res.on('data', function(chunk) {
bodyChunks.push(chunk);
}).on('end', function() {
var body = Buffer.concat(bodyChunks);
var parsedBody = JSON.parse(body);
post.fullname = parsedBody.user.fullname;
post.profilePic = parsedBody.user.profilePic;
});
});
});
res.json({
posts : posts
});
} else {
res.send('Post does not exist');
}
});
});
At the time of the post.profilePic = parsedBody.user.profilePic - the profilePic variable is there but when I get a response from node via res.json, the additional values are not.
What am I missing here? I use this approach with my Angular frontend all the time without an issue.
Thanks
This is an incredibly common problem, you are treating asynchronous code as if it were synchronous. http.get does not complete immediately, nor does it block the code from continuing on, therefore res.json is called before your requests complete. There are a ton of ways to fix this, I'll post my favorite - Javascript Promises.
// use map instead of forEach to transform your array
// of posts into an array of promises
var postPromises = posts.map(function(post) {
return new Promise(function(resolve) {
var req = http.get(options, function(res) {
var bodyChunks = [];
res.on('data', function(chunk) {
bodyChunks.push(chunk);
}).on('end', function() {
var body = Buffer.concat(bodyChunks);
var parsedBody = JSON.parse(body);
post.fullname = parsedBody.user.fullname;
post.profilePic = parsedBody.user.profilePic;
// resolve the promise with the updated post
resolve(post);
});
});
});
});
// once all requests complete, send the data
Promise.all(postPromises).then(function(posts) {
res.json({
posts: posts
});
});
Node work according to callbacks. You callbacks not completed in the forEach Loop and you are giving response to user. This is the issue.
Write code for that I can suggest solution.
router.get('/api/posts', function(req, res){
postModel.find({})
.limit(10)
.exec(function(err, posts) {
var options = {
host: 'localhost',
port: 3000,
path: '/user?id=12345678',
method: 'GET'
};
if(posts){
var EventEmitter = require('events');
var HttpEvent = new EventEmitter();
let counts = 0;
let length = posts.length;
posts.forEach(function(post) {
var req = http.get(options, function(res) {
var bodyChunks = [];
res.on('data', function(chunk) {
bodyChunks.push(chunk);
}).on('end', function() {
var body = Buffer.concat(bodyChunks);
var parsedBody = JSON.parse(body);
posts.fullname = parsedBody.user.fullname;
posts.profilePic = parsedBody.user.profilePic;
HttpEvent.emit('done');
});
});
});
HttpEvent.on('done',()=>{
counts += 1;
if(counts == length){
res.json({
posts : posts
});
}
})
} else {
res.send('Post does not exist');
}
});
});
One more wrong thing you are doing is
post.fullname = parsedBody.user.fullname;
post.profilePic = parsedBody.user.profilePic;
It should be
posts.fullname = parsedBody.user.fullname;
posts.profilePic = parsedBody.user.profilePic;
Below I am trying to repond to a GET with the weather in JSON format. I am attempting the use the const done in order to deliver the response. This does not seem to work. I get the response and weather in console but return nothing to the client.
'use strict';
console.log('Loading function');
const doc = require('dynamodb-doc');
const http = require('http');
const dynamo = new doc.DynamoDB();
function get_json(url, callback) {
http.get(url, function(res) {
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
var response = JSON.parse(body);
callback(response);
});
});
}
exports.handler = (event, context, callback) => {
const done = (err, res) => callback(null, {
statusCode: err ? '400' : '200',
body: err ? err.message : JSON.stringify(res),
headers: {
'Content-Type': 'application/json',
},
});
switch (event.httpMethod) {
case 'DELETE':
dynamo.deleteItem(JSON.parse(event.body), done)
break;
case 'GET':
// dynamo.scan({ TableName: event.queryStringParameters.TableName }, done);
done(get_json("http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=92f14e06a6652e81a5a58bd13d152f70", callback, function (resp) {
callback(resp);
}));
break;
}
};
get_json() doesn't return anything, so passing it into done is definitely the wrong thing to do.
It looks like you need to do this:
get_json(
"http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=92f14e06a6652e81a5a58bd13d152f70",
resp => done(null, resp)
);
i have an application which needs a data.json file in order to draw a d3-graph. However i need to update that file on an onClick-Event:
d3.select("#updatebutton").on("click", function(e) {
try{
$.get('https://localhost:4444/data', function(data) {
});
}
catch (e) {
alert('Error: ' + e);
}
});
Above is the update-Button with the jquery-call. In my app.js File I am using it like this:
app.get('/data', function(req, res, next) {
try{
getJSON();
}
catch(e) {
alert('Error');
}
});
The getJSON()-Function is received Data over an https-Request, processes that data and saves it to data.json:
function getJSON() {
var req = https.get(options, function(response) {
// handle the response
var res_data = '';
response.on('data', function(chunk) {
res_data += chunk;
});
response.on('end', function() {
//process data
// save to file
fs.writeFile(filePath, JSON.stringify(finalJson), function(err) {
if (err)
throw err;
});
});
});
}
However if i click on my updateButton repeatedly after seconds, it seems that data.json is not overwritten but the file gets bigger and bigger, means that data is added to the file instead of overwritten.
What am I doing wrong?
Thanks for help.
Since you use app.get as your route, I guess you are using express.
In your routes definition:
var getData = (function() {
var callbacks = [];
function executeCallbacks(err, data) {
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](err, data);
}
callbacks = [];
}
return function(cb) {
callbacks.push(cb);
if( callbacks.length === 1 ) {
var req = https.get(options, function(response) {
// handle the response
var res_data = '';
response.on('data', function(chunk) {
res_data += chunk;
});
response.once('end', function() {
// process data here
// save to file
fs.writeFile(filePath, JSON.stringify(finalJson), function(err) {
if (err) {
// call error handler
return executeCallbacks(err);
}
executeCallbacks(null, body);
});
});
response.once('error', function() {
return executeCallbacks(err);
});
}
req.end();
}
};
})();
app.get('/data', function(req, res, next) {
getData(function(err, data) {
if(err) {
return next(err);
}
return data;
});
});
In your browser js file:
d3.select("#updatebutton").on("click", function(e) {
$.get( 'https://localhost:4444/data', function(data) {
alert( "success" );
var json = JSON.parse(data);
})
.fail(function() {
alert( "error" );
});
});
I see you use try / catch around callback functions. The callback function fires after the original function completes. So don't use Try / Catch around callback function.
Read: https://strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/
Is it sensible to use Node.js to write a stand alone app that will connect two REST API's?
One end will be a POS - Point of sale - system
The other will be a hosted eCommerce platform
There will be a minimal interface for configuration of the service. nothing more.
Yes, Node.js is perfectly suited to making calls to external APIs. Just like everything in Node, however, the functions for making these calls are based around events, which means doing things like buffering response data as opposed to receiving a single completed response.
For example:
// get walking directions from central park to the empire state building
var http = require("http");
url = "http://maps.googleapis.com/maps/api/directions/json?origin=Central Park&destination=Empire State Building&sensor=false&mode=walking";
// get is a simple wrapper for request()
// which sets the http method to GET
var request = http.get(url, function (response) {
// data is streamed in chunks from the server
// so we have to handle the "data" event
var buffer = "",
data,
route;
response.on("data", function (chunk) {
buffer += chunk;
});
response.on("end", function (err) {
// finished transferring data
// dump the raw data
console.log(buffer);
console.log("\n");
data = JSON.parse(buffer);
route = data.routes[0];
// extract the distance and time
console.log("Walking Distance: " + route.legs[0].distance.text);
console.log("Time: " + route.legs[0].duration.text);
});
});
It may make sense to find a simple wrapper library (or write your own) if you are going to be making a lot of these calls.
Sure. The node.js API contains methods to make HTTP requests:
http.request
http.get
I assume the app you're writing is a web app. You might want to use a framework like Express to remove some of the grunt work (see also this question on node.js web frameworks).
/*Below logics covered in below sample GET API
-DB connection created in class
-common function to execute the query
-logging through bunyan library*/
const { APIResponse} = require('./../commonFun/utils');
const createlog = require('./../lib/createlog');
var obj = new DB();
//Test API
routes.get('/testapi', (req, res) => {
res.status(201).json({ message: 'API microservices test' });
});
dbObj = new DB();
routes.get('/getStore', (req, res) => {
try {
//create DB instance
const store_id = req.body.storeID;
const promiseReturnwithResult = selectQueryData('tablename', whereField, dbObj.conn);
(promiseReturnwithResult).then((result) => {
APIResponse(200, 'Data fetched successfully', result).then((result) => {
res.send(result);
});
}).catch((err) => { console.log(err); throw err; })
} catch (err) {
console.log('Exception caught in getuser API', err);
const e = new Error();
if (err.errors && err.errors.length > 0) {
e.Error = 'Exception caught in getuser API';
e.message = err.errors[0].message;
e.code = 500;
res.status(404).send(APIResponse(e.code, e.message, e.Error));
createlog.writeErrorInLog(err);
}
}
});
//create connection
"use strict"
const mysql = require("mysql");
class DB {
constructor() {
this.conn = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'pass',
database: 'db_name'
});
}
connect() {
this.conn.connect(function (err) {
if (err) {
console.error("error connecting: " + err.stack);
return;
}
console.log("connected to DBB");
});
}
//End class
}
module.exports = DB
//queryTransaction.js File
selectQueryData= (table,where,db_conn)=>{
return new Promise(function(resolve,reject){
try{
db_conn.query(`SELECT * FROM ${table} WHERE id = ${where}`,function(err,result){
if(err){
reject(err);
}else{
resolve(result);
}
});
}catch(err){
console.log(err);
}
});
}
module.exports= {selectQueryData};
//utils.js file
APIResponse = async (status, msg, data = '',error=null) => {
try {
if (status) {
return { statusCode: status, message: msg, PayLoad: data,error:error }
}
} catch (err) {
console.log('Exception caught in getuser API', err);
}
}
module.exports={
logsSetting: {
name: "USER-API",
streams: [
{
level: 'error',
path: '' // log ERROR and above to a file
}
],
},APIResponse
}
//createlogs.js File
var bunyan = require('bunyan');
const dateFormat = require('dateformat');
const {logsSetting} = require('./../commonFun/utils');
module.exports.writeErrorInLog = (customError) => {
let logConfig = {...logsSetting};
console.log('reached in writeErrorInLog',customError)
const currentDate = dateFormat(new Date(), 'yyyy-mm-dd');
const path = logConfig.streams[0].path = `${__dirname}/../log/${currentDate}error.log`;
const log = bunyan.createLogger(logConfig);
log.error(customError);
}
A more easy and useful tool is just using an API like Unirest; URest is a package in NPM that is just too easy to use jus like
app.get('/any-route', function(req, res){
unirest.get("https://rest.url.to.consume/param1/paramN")
.header("Any-Key", "XXXXXXXXXXXXXXXXXX")
.header("Accept", "text/plain")
.end(function (result) {
res.render('name-of-the-page-according-to-your-engine', {
layout: 'some-layout-if-you-want',
markup: result.body.any-property,
});
});