So I am making a kind of API middleware for my company that will grab information from the NOAA API and then store in in my database. It does more then but that a separate part. I have set it up so that it works it will get the information and store it in my sql database perfectly The issue is the information I get is based off of zipcode. One request is the information for one zipcode. I need to be able to 'loop" through a list of zipcode one at a time and store the information in the database. I am not sure how to properly get it to work. I have tested a couple of ways but have not been able to get it to work so if someone can get me pointed in the right direction it would be appreciated.
Sorry in advance my code is not cleaned up.
Everything below apiRequest.end() has little function for the question. I keep it for context.
let mysql = require('mysql');
let config = require('./config.js');
var https = require("https");
var express = require("express");
var app = express();
const port = 3000;
var fs= require('fs');
var csv = require('fast-csv');
//last test
//array will replace this zip variable
let zip = '90012';
api(zip);
function api(zips){
//All of the parts for building the get requests url
app.get("/", function(req, response) {
var apiKey = "gPaEVizejLlbRVbXexyWtXYkfkWkoBhd";
let webapi = 'https://www.ncdc.noaa.gov/cdo-web/api/v2/data?';
let datasetid="datasetid=GHCND";
let datatypeid="&datatypeid=TMAX";
let location="&locationid=ZIP:";
const zipcode = zips;
let startdate="&startdate=2019-01-01";
let enddate="&enddate=2020-01-01";
let units = "&units=standard";
let limit="&limit=1000";
let url = webapi + datasetid + datatypeid + location + zipcode + startdate + enddate + units + limit;
var options = {
port: 443,
method: "GET",
headers: {
"token": apiKey
}
};
let data = "";
//request to grab from NOAA api
let apiRequest = https.request(url, options, function(res) {
console.log("Connected");
//grabing all data
res.on("data", chunk => {
data += chunk;
});
res.on("end", () => {
console.log("data collected");
//Format JSON data
response.send(JSON.parse(data));
var getData = JSON.parse(data);
if(isEmpty(getData)){
emptyCorrect();
}
dataFormat(getData);
});
});
apiRequest.end();
});
//fix format for date Can add more formating if needed here
function dataFormat(formData){
for(x in formData.results){
let date = formData.results[x].date;
formData.results[x].date = date.slice(0,10);
}
jsonToSQL(formData.results);
}
//test function is going to be used for inserting the zip
function test(){
var content = "";
console.log("your test worked see ***************");
return "92507";
}
//function to add grabed JSON data into the SQL database
function jsonToSQL(datafin){
var zipcode = zips;
let connection = mysql.createConnection(config);
// insert statment
let stmt = `INSERT INTO test1(ZIPCODE,DATE, TEMP) VALUES ? `;
let values = [];
for(let x in datafin){
values.push([zipcode,datafin[x].date,datafin[x].value]);
}
// execute the insert statment
connection.query(stmt, [values], (err, results, fields) => {
if (err) {
return console.error("error");
}
// get inserted rows
console.log('Row inserted:' + results.affectedRows);
});
// close the database connection
connection.end();
}
function emptyCorrect(){
console.log("Eror correction");
var zipcode = zips;
let connection = mysql.createConnection(config);
// insert statment
let stmt = `INSERT INTO test1(ZIPCODE,DATE, TEMP) VALUES ? `;
let valueE = [];
valueE.push([zipcode,"0","No Data"]);
// execute the insert statment
connection.query(stmt, [valueE], (err, results, fields) => {
if (err) {
return console.error("error");
}
// get inserted rows
console.log('Row inserted:' + results.affectedRows);
});
// close the database connection
connection.end();
}
function isEmpty(obj) {
for(var key in obj) {
if(obj.hasOwnProperty(key))
return false;
}
return true;
}
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
}
As I understand your problem can roughly be summarized as "How to loop through asynchronous evaluations in Nodejs".
There are some options for you. I would recommend wrapping call to the NOAA API with a promise and then chain those promises. This can be done as follows:
app.get('/', async function(req, response) {
var apiKey = 'some value';
let webapi = 'https://www.ncdc.noaa.gov/cdo-web/api/v2/data?';
let datasetid = 'datasetid=GHCND';
let datatypeid = '&datatypeid=TMAX';
let location = '&locationid=ZIP:';
let startdate = '&startdate=2019-01-01';
let enddate = '&enddate=2020-01-01';
let units = '&units=standard';
let limit = '&limit=1000';
var options = {
port: 443,
method: 'GET',
headers: {
token: apiKey
}
};
const zipCodes = ['90012', '90013']; // Place a call to your function for fetching zip codes here
let datas = [];
prom = Promise.resolve();
zipCodes.forEach(zipcode => {
prom = prom.then(() =>
new Promise((resolve, reject) => {
let url =
webapi +
datasetid +
datatypeid +
location +
zipcode +
startdate +
enddate +
units +
limit;
let apiRequest = https.request(url, options, function(res) {
console.log('Connected');
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
console.log('data collected for zip ' + zipcode);
datas.push(data);
resolve();
});
});
apiRequest.end();
})
);
});
prom.then(() => {
// All requests have now been handled sequentially
response.send(/* You'll need to figure out what to do here */);
});
});
An alternative is to use something like the async library for dealing with sequentially calling callbacks. The async library (https://github.com/caolan/async) describes itself as:
Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript.
See e.g. Node.js: How do you handle callbacks in a loop? for a similar problem (not with regards to callign an API, but dealing with asynchronous function in a loop).
I7m trying to create a simple http2 server and want to make use of Http2Stream in the http2 module to push large assets. How can I incorporate it in my Koa2 app? currently my server is just a middleware that receives the ctx and next object and checks if the file exists and tries to send it out.
async server(ctx, next, ...arg){
//check if file exists
//if it exists, set the headers and mimetype and send file
}
Does the ctx object contain the functions needed to work with http2stream, or how can i extend it?
You can utilize the stream in ctx.res (which is the original node response) like so: ctx.res.stream
Working example: Koa2 with http/2 - this one gets a file in the public folder (filename ist hardcoded here) and sends it through the stream (which then should be the http2stream). Just type https://localhost:8080/file in your browser. You need to place a file thefile.html in to ./public:
'use strict';
const fs = require('fs');
const http2 = require('http2');
const koa = require('koa');
const app = new koa();
const options = {
key: fs.readFileSync('./key.pem'),
cert: fs.readFileSync('./cert.pem'),
passphrase: 'test'
};
function getFile(path) {
const filePath = `${__dirname}/public/${path}`;
try {
const content = fs.openSync(filePath, 'r');
const contentType = 'text/html';
return {
content,
headers: {
'content-type': contentType
}
};
} catch (e) {
return null;
}
}
// response
app.use(ctx => {
if (ctx.request.url === '/file') {
const file = getFile('thefile.html');
ctx.res.stream.respondWithFD(file.content, file.headers);
} else {
ctx.body = 'OK' ;
}
});
const server = http2.createSecureServer(options, app.callback());
console.log('Listening on port 8080');
server.listen(8080);
Hope that helps
I'm given an endpoint that returns a CSV file. I want to hit that end point, read the data, and send it to the client. This is what I have right now.
app.get('/data', (req, res) => {
request('foo.com/downloadCSV', (error, response, body) => {
res.send(csvToJson(body));
});
});
So on the front end, I hit '/data', which then uses the request library to make the call to get the CSV data. The body for the CSV is in 'body'. The 'csvToJson' function just formats the data given into arrays/json.
When I console.log the response on my client, my body is: ReadableStream, locked(...)
How do I get the data to the client?
Edit:
Here is my csvToJson function:
const csvToJson = (csv) => {
const content = csv.split('\r');
const header = content[0].split(',');
return _.tail(content).map((row) => {
return _.zipObject(header, row.split(','));
});
}
I've tried your code and it works. In public folder I've created sample.csv.
var express = require('express');
var app = express();
var request = require('request');
var _ = require('lodash');
app.use(express.static('public'));
app.get('/data', (req, res) => {
request('http://localhost:3000/sample.csv', (error, response, body) => {
res.send(csvToJson(body));
});
});
const csvToJson = (csv) => {
const content = csv.split('\n');
const header = content[0].split(',');
return _.tail(content).map((row) => {
return _.zipObject(header, row.split(','));
});
}
app.listen(3000, function () {
console.log('Listening on port 3000!')
})
Sample.csv file:
a,b,c,d
1,2,3,4
2,1,5,6
54,3,1,12
1,2,3,4
Result:
Problem maybe is with your csv file which your are downloading from other service.
My React front end:
componentDidMount() {
fetch('/data').then(response => {
var decoder = new TextDecoder();
var reader = response.body.getReader();
// read() returns a promise that resolves
// when a value has been received
reader.read().then(function processResult(result) {
if (result.done) return;
console.log(
decoder.decode(result.value, {stream: true})
);
// Read some more, and recall this function
return reader.read().then(processResult);
});
});
}
This will end the ReadableStream and get the data out of it.
The title should be pretty self explanetory.
For debugging purposes, I would like express to print the response code and body for every request serviced. Printing the response code is easy enough, but printing the response body is trickier, since it seems the response body is not readily available as a property.
The following does NOT work:
var express = require('express');
var app = express();
// define custom logging format
express.logger.format('detailed', function (token, req, res) {
return req.method + ': ' + req.path + ' -> ' + res.statusCode + ': ' + res.body + '\n';
});
// register logging middleware and use custom logging format
app.use(express.logger('detailed'));
// setup routes
app.get(..... omitted ...);
// start server
app.listen(8080);
Of course, I could easily print the responses at the client who emitted the request, but I would prefer doing at the server side too.
PS: If it helps, all my responses are json, but hopefully there is a solution that works with general responses.
Not sure if it's the simplest solution, but you can write a middleware to intercept data written to the response. Make sure you disable app.compress().
function logResponseBody(req, res, next) {
var oldWrite = res.write,
oldEnd = res.end;
var chunks = [];
res.write = function (chunk) {
chunks.push(chunk);
return oldWrite.apply(res, arguments);
};
res.end = function (chunk) {
if (chunk)
chunks.push(chunk);
var body = Buffer.concat(chunks).toString('utf8');
console.log(req.path, body);
oldEnd.apply(res, arguments);
};
next();
}
app.use(logResponseBody);
I ran into an issue using the approach suggested by Laurent. Sometimes chunk is a string, and therefore causes problems in the call to Buffer.concat(). Anyways, I found a slight modification fixed things:
function logResponseBody(req, res, next) {
var oldWrite = res.write,
oldEnd = res.end;
var chunks = [];
res.write = function (chunk) {
chunks.push(new Buffer(chunk));
oldWrite.apply(res, arguments);
};
res.end = function (chunk) {
if (chunk)
chunks.push(new Buffer(chunk));
var body = Buffer.concat(chunks).toString('utf8');
console.log(req.path, body);
oldEnd.apply(res, arguments);
};
next();
}
app.use(logResponseBody);
The above accepted code has issues with ES6.
Use the below code
function logReqRes(req, res, next) {
const oldWrite = res.write;
const oldEnd = res.end;
const chunks = [];
res.write = (...restArgs) => {
chunks.push(Buffer.from(restArgs[0]));
oldWrite.apply(res, restArgs);
};
res.end = (...restArgs) => {
if (restArgs[0]) {
chunks.push(Buffer.from(restArgs[0]));
}
const body = Buffer.concat(chunks).toString('utf8');
console.log({
time: new Date().toUTCString(),
fromIP: req.headers['x-forwarded-for'] ||
req.connection.remoteAddress,
method: req.method,
originalUri: req.originalUrl,
uri: req.url,
requestData: req.body,
responseData: body,
referer: req.headers.referer || '',
ua: req.headers['user-agent']
});
// console.log(body);
oldEnd.apply(res, restArgs);
};
next();
}
module.exports = logReqRes;
You can use express-winston and configure using:
expressWinston.requestWhitelist.push('body');
expressWinston.responseWhitelist.push('body');
Example in coffeescript:
expressWinston.requestWhitelist.push('body')
expressWinston.responseWhitelist.push('body')
app.use(expressWinston.logger({
transports: [
new winston.transports.Console({
json: true,
colorize: true
})
],
meta: true, // optional: control whether you want to log the meta data about the request (default to true)
msg: "HTTP {{req.method}} {{req.url}}", // optional: customize the default logging message. E.g. "{{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}"
expressFormat: true, // Use the default Express/morgan request formatting, with the same colors. Enabling this will override any msg and colorStatus if true. Will only output colors on transports with colorize set to true
colorStatus: true, // Color the status code, using the Express/morgan color palette (default green, 3XX cyan, 4XX yellow, 5XX red). Will not be recognized if expressFormat is true
ignoreRoute: function (req, res) { return false; } // optional: allows to skip some log messages based on request and/or response
}));
This solution might not be heavyweight enough for some use cases, but I think it's the simplest. It's also typescript compatible. If you only want logging for JSON responses, all you have to do is substitute the send method with the json method in the code below. Note, I took inspiration from Jonathan Turnock's answer, but made it simpler.
app.use((req, res, next) => {
let send = res.send;
res.send = c => {
console.log(`Code: ${res.statusCode}`);
console.log("Body: ", c);
res.send = send;
return res.send(c);
}
next();
});
I found the simplest solution to this problem was to add a body property to the res object when sending the response, which can later be accessed by the logger. I add this to my own namespace that I maintain on the req and res objects to avoid naming collisions. e.g.
res[MY_NAMESPACE].body = ...
I have a utility method that formats all responses to my standardized API/JSON response, so adding this one liner there exposed the response body when the logging gets triggered by onFinished event of res.
Most of the suggestions seemed a little sledgehammer, Spent some time with this issue tonight and wrote up my findings after digging into a few libs to help make something bespoke.
//app.js
...
app.use(requestLoggerMiddleware({ logger: console.log }));
app.get(["/", "/api/health"], (req, res) => {
res.send({ message: "OK", uptime: process.uptime() });
...
});
// middleware.js
/**
* Interceptor function used to monkey patch the res.send until it is invoked
* at which point it intercepts the invokation, executes is logic such as res.contentBody = content
* then restores the original send function and invokes that to finalize the req/res chain.
*
* #param res Original Response Object
* #param send Original UNMODIFIED res.send function
* #return A patched res.send which takes the send content, binds it to contentBody on
* the res and then calls the original res.send after restoring it
*/
const resDotSendInterceptor = (res, send) => (content) => {
res.contentBody = content;
res.send = send;
res.send(content);
};
/**
* Middleware which takes an initial configuration and returns a middleware which will call the
* given logger with the request and response content.
*
* #param logger Logger function to pass the message to
* #return Middleware to perform the logging
*/
const requestLoggerMiddleware = ({ logger }) => (req, res, next) => {
logger("RECV <<<", req.method, req.url, req.hostname);
res.send = resDotSendInterceptor(res, res.send);
res.on("finish", () => {
logger("SEND >>>", res.contentBody);
});
next();
};
module.exports = { requestLoggerMiddleware };
Full working example and article in the git repo
https://github.com/JonathanTurnock/ReqResLoggingExample
I actually made this nifty little npm to solve this exact problem, hope you like it!
https://www.npmjs.com/package/morgan-body
May be this would help someone who is looking to get the response logged
So, we use the middleware to intercept the request just before being served to the client. Then if we are using res.send method to send the data, override the method in the middleware and make sure to console log the body. If you are planning to use res.send alone then this should work fine, but incase if you use res.end or res.sendFile, then overwrite those methods and log only the required things (obviously logging the entire octet stream of file should never be logged for perfomance purposes.
Here I use pino as the logger. Created it as singleton service.
// LoggingResponseRouter.js
var loggingResponseRouter = require('express').Router();
var loggingService = require('./../service/loggingService');
var appMethodInstance = require('./../constants/appMethod');
var path = require('path');
var fs = require('fs');
var timeZone = require('moment-timezone');
var pino = require('pino')();
loggingResponseRouter.use((req, res, next) => {
// set the fileName it needs to log
appMethodInstance.setFileName(__filename.substring(__filename.lastIndexOf(path.sep) + 1, __filename.length - 3));
//loggingService.debugAndInfolog().info('logging response body', appMethodInstance.getFileName());
let send = res.send;
res.send = function(body){
loggingService.debugAndInfolog().info('Response body before sending: ', body);
send.call(this, body);
}
next();
});
module.exports = loggingResponseRouter;
Main file - Main.js
const corsRouter = require('./app/modules/shared/router/corsRouter');
const logRequestRouter = require('./app/modules/shared/router/loggingRequestRouter');
const loggingResponseRouter = require('./app/modules/shared/router/loggingResponseRouter');
const express = require('express');
var path = require('path');
const app = express();
// define bodyparser middleware
const bodyParser = require('body-parser');
const port = process.env.PORT || 3000;
// Now use the middleware prior to any others
app.use(bodyParser.json());
// use this to read url form encoded values as wwell
app.use(bodyParser.urlencoded({extended:true}));
console.log('before calling cors router in main js');
app.use(corsRouter);
app.use(logRequestRouter);
app.use(loggingResponseRouter);
app.get('/api', (req, res) => {
console.log('inside api call');
res.send('aapi');
});
app.listen(port, () => {
console.log('starting the server');
});
And this is the loggingService - loggingService.js
var pino = require('pino');
var os = require('os');
var appMethodInstance = require('./../constants/appMethod');
var pinoPretty = require('pino-pretty');
var moment = require('moment');
var timeZone = require('moment-timezone');
class Logger{
constructor(){
this.appName = 'Feedback-backend';
this.filenameval = '';
}
getFileName(){
console.log('inside get filename');
console.log(appMethodInstance.getFileName());
if(appMethodInstance.getFileName() === null || appMethodInstance.getFileName() === undefined){
this.filenameval = 'bootstrapping...'
}else {
this.filenameval = appMethodInstance.getFileName();
}
console.log('end');
return this.filenameval;
}
debugAndInfolog(){
return pino({
name: 'feedback-backend',
base: {
pid: process.pid,
fileName: this.getFileName(),
moduleName: 'modulename',
timestamp: timeZone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss.ms'),
hostName: os.hostname()
},
level: 'info',
timestamp: timeZone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss.ms'),
messageKey: 'logMessage',
prettyPrint: {
messageKey: 'logMessage'
}
});
}
errorAndFatalLog(){
return pino({
name: 'feedback-backend',
base: {
pid: process.pid,
fileName: this.getFileName(),
moduleName: 'modulename',
timestamp: timeZone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss.ms'),
hostName: os.hostname()
},
level: 'error',
timestamp: timeZone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss.ms'),
prettyPrint: {
messageKey: 'FeedbackApp'
}
});
}
}
module.exports = new Logger();
Typescript solution based on Laurent's answer:
import { NextFunction, Request, Response } from 'express-serve-static-core';
//...
app.use(logResponseBody);
function logResponseBody(req: Request, res: Response, next: NextFunction | undefined) {
const [oldWrite, oldEnd] = [res.write, res.end];
const chunks: Buffer[] = [];
(res.write as unknown) = function(chunk) {
chunks.push(Buffer.from(chunk));
(oldWrite as Function).apply(res, arguments);
};
res.end = function(chunk) {
if (chunk) {
chunks.push(Buffer.from(chunk));
}
const body = Buffer.concat(chunks).toString('utf8');
console.log(new Date(), ` ↪ [${res.statusCode}]: ${body}`);
(oldEnd as Function).apply(res, arguments);
};
if (next) {
next();
}
}
I have similar need to this question.
Based on accepted answer, I modify it with proxy and trace response body only when it's json.
const traceMiddleware = (req, res, next) => {
const buffers = []
const proxyHandler = {
apply(target, thisArg, argumentsList) {
const contentType = res.getHeader('content-type')
if (
typeof contentType === 'string' && contentType.includes('json') && argumentsList[0]
) {
buffers.push(argumentsList[0])
}
return target.call(thisArg, ...argumentsList)
}
}
res.write = new Proxy(res.write, proxyHandler)
res.end = new Proxy(res.end, proxyHandler)
res.on('finish', () => {
// tracing logic inside
trace(req, res, Buffer.concat(buffers).toString('utf8'))
})
next()
}
I have a windows 8 application connecting to a web service written in Node.js. On the windows 8 side I compressed my request body to gzip. But on the Node.js side I found that my req.body type was Object.
I cannot use zlib to uncomporess the body since it's not a stream.
I can use zlib to uncomporess the req, but I don't know how to retrieve the req.body content from the unzipped stream and parse the body in JSON format.
BTW, I reviewed my request through Fiddler and it told me the request body was gzipped, and I can see my raw body through Fiddler after unzipped so the request should be correct.
Updated
Below is my Node.js app
(function () {
var express = require("express");
var zlib = require("zlib");
var app = express();
var port = 12345;
app.configure(function () {
app.use(express.compress());
app.use(express.bodyParser());
});
app.post("/test", function (req, res) {
var request = req.body;
req.pipe(zlib.createGunzip());
var response = {
status: 0,
value: "OK"
};
res.send(200, response);
});
console.log("started at port %d", port);
app.listen(port);
})();
And below is my windows store app code (partial)
private async void button1_Click_1(object sender, RoutedEventArgs e)
{
var message = new
{
Name = "Shaun",
Value = "12345678901234567890123456789012345678901234567890"
};
var json = await JsonConvert.SerializeObjectAsync(message, Formatting.Indented);
var bytes = Encoding.UTF8.GetBytes(json);
var client = new HttpClient();
client.BaseAddress = new Uri("http://192.168.56.1:12345/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.ExpectContinue = false;
var jsonContent = new JsonContent(message);
var gzipContent = new GZipContent3(jsonContent);
var res = await client.PostAsync("test", gzipContent);
var dialog = new Windows.UI.Popups.MessageDialog(":)", "完成");
await dialog.ShowAsync();
}
internal class GZipContent3 : ByteArrayContent
{
public GZipContent3(HttpContent content)
: base(LoadGZipBytes(content))
{
//base.Headers.ContentType = content.Headers.ContentType;
base.Headers.ContentType = new MediaTypeHeaderValue("x-application/x-gzip");
base.Headers.ContentEncoding.Add("gzip");
}
private static byte[] LoadGZipBytes(HttpContent content)
{
var source = content.ReadAsByteArrayAsync().Result;
byte[] buffer;
using (var outStream = new MemoryStream())
{
using (var gzip = new GZipStream(outStream, CompressionMode.Compress, true))
{
gzip.Write(source, 0, source.Length);
}
buffer = outStream.ToArray();
}
return buffer;
}
}
internal class JsonContent : StringContent
{
private const string defaultMediaType = "application/json";
public JsonContent(string json)
: base(json)
{
var mediaTypeHeaderValue = new MediaTypeHeaderValue(defaultMediaType);
mediaTypeHeaderValue.CharSet = Encoding.UTF8.WebName;
base.Headers.ContentType = mediaTypeHeaderValue;
}
public JsonContent(object content)
: this(GetJson(content))
{
}
private static string GetJson(object content)
{
if (content == null)
{
throw new ArgumentNullException("content");
}
var json = JsonConvert.SerializeObject(content, Formatting.Indented);
return json;
}
}
http://www.senchalabs.org/connect/json.html. Basically you need to write your own middleware based on connect.json() that pipes through an uncompression stream like connect.compress() but the opposite direction: http://www.senchalabs.org/connect/compress.html
Also, make sure you're sending the correct Content-Encoding header in your request.
If you show me what you have so far I can help you further.
I was working on similar thing and finally landed on
function getGZipped(req, callback) {
var gunzip = zlib.createGunzip();
req.pipe(gunzip);
var buffer = [];
gunzip.on('data', function (data) {
// decompression chunk ready, add it to the buffer
buffer.push(data);
}).on('end', function () {
//response and decompression complete, join the buffer and return
callback(null, JSON.parse(buffer));
}).on('error', function (e) {
callback(e);
});
}