What's the best approach to streaming MongoDB query responses to the client via Hapi? I've seen some examples with http or request, but not hapi.
The problem is that I'm getting concatenated and stringified JSON objects on the client side, but I can't can't call JSON.parse on the result because together it's not valid JSON.
Some solutions I've seen suggest concatenating on the server side before sending to the client, but that seems to defeat the value of streams.
For example:
const Hapi = require('hapi'),
MongoClient = require('mongodb').MongoClient,
Readable = require('stream').Readable;
// Connection url
const url = 'mongodb://localhost:27017/test';
// Create a server with a host and port
const server = new Hapi.Server();
server.connection({
host: 'localhost',
port: 8000
});
// Add the route
server.route({
method: 'GET',
path: '/stream',
handler: function (request, reply) {
let docs = [{ a: 1, b: 1 }, { a: 2, b: 2 }, { a: 3, b: 3 }, { a: 4, b: 4 }];
// Connect using MongoClient
MongoClient.connect(url, (err, db) => {
// Create a collection we want to drop later
const col = db.collection('stream_example');
// Insert documents into collection
col.insertMany(docs, { w: 1 }, function (err) {
if (err) return console.log(err);
// Peform a find to get a cursor
const stream = col.find()
.stream({
transform: function (doc) {
return JSON.stringify(doc);
}
});
reply(new Readable().wrap(stream));
});
});
}
});
// Start the server
server.start(err => {
if (err) {
throw err;
}
console.log('Server running at:', server.info.uri);
});
Returns a response.result of:
"{"_id":"57b0b99d681bb97a9321f03e","a":1,"b":1}{"_id":"57b0b99d681bb97a9321f03f","a":2,"b":2}{"_id":"57b0b99d681bb97a9321f040","a":3,"b":3}{"_id":"57b0b99d681bb97a9321f041","a":4,"b":4}"
Which is not valid JSON and cannot be parsed.
I've tried piping this stream into the event-stream module's .join('\n') stream to add newlines while also pushing string'd "[" and "]" before and after to build a stringified JSON Array, but have not yet been successful. This feels too hacky anyways.
Is there a better way?
A valid JSON has to be sent by using a stream transform.
Basically you have to:
start the stream with '['
then append stringified JSON object
add ',' after each of the objects
end stream with ']'
so the final result recevied in the stream would be valid JSON, like
[
{'key': 'value'},
{'key': 'value'},
]
Some of the solutions:
http://andyfiedler.com/2017/01/mongodb-stream-to-hapi
https://github.com/nlindley/hapi-mongo-stream
https://github.com/dominictarr/JSONStream
Here is an example of how I have been using Mongo with Hapi.
From BoardRepo.js:
module.exports = {
GetAllBoards: function (facility) {
return new Promise(function (resolve, reject) {
var db = mongo.ETestDatabase;
db.collection('boards').find({ "Location.Facility": facility }).toArray().then(r => {
resolve(r);
}).catch(err => {
logger.error('Error getting all boards by facility - ' + err.message);
reject(err.message);
});
});
}
};
In the Hapi handler (BoardHandler.js):
module.exports = {
GetAllBoards: {
method: 'GET',
path: '/v1/boards/facility/all/{facility}',
config: {
auth: 'jwt',
plugins: { 'hapiAuthorization': { roles: ['ADMINISTRATOR', 'MANAGER', 'TECHNICIAN', 'OPERATOR'] } },
description: 'Gets all boards per facility',
tags: ['api'],
handler: (request, reply) => {
logger.info('[' + request.auth.credentials.username + '] GetAllBoards requested');
var facility = request.params.facility;
repo.GetAllBoards(facility)
.then(boards => {
if (boards !== null) {
reply(boards);
} else {
reply().code(404);
}
})
.catch(err => {
geh.HandleError(request.auth.credentials.username, err, reply);
});
}
}
}
};
Related
I'm trying to create function in AWS Lambda (node.js), which call some REST API, dan insert the API result to MySQL DB.
While the requirement is very simple, but I encounter some problem when deploying to AWS Lambda (not happening on my local machine), where my first API call only resulting only 1 data is inserted, while the second API call forward, it insert all 4 data as intended. I try various solution available on stack overflow, and all resulting the same.
Another problem is that the result is always {"message": "Internal server error"}, even though the data is inserted correctly on second API call forwards
Basically i don't have much experience with Node.js, so i would appreciate if anyone could help me.
'use strict';
const connection = require('serverless-mysql')({
config: {
host: 'xxxxxx.xxxxx.ap-southeast-1.rds.amazonaws.com',
user: 'xxx',
password: 'xxx',
database: 'xxx_db'
}
})
const axios = require('axios');
exports.handler = (event, context) => {
//Get Data From API
axios.get('https://xxx.xyz/wp-json/wp/v2/posts')
.then(res => {
const headerDate = res.headers && res.headers.date ? res.headers.date : 'no response date';
console.log('Status Code:', res.status);
console.log('Date in Response header:', headerDate);
//this should result 4 data
const posts = res.data;
posts.forEach(post => {
var sql = `INSERT INTO tbl_post(news_id, title, excerpt, content, category, image_link, modified_date, show_in_banner_F, show_in_list_F) VALUES ('${post.id}', '${post.title.rendered}', '${post.excerpt.rendered}', '${post.content.rendered}', '', '${post.yoast_head_json.og_image[0].url}', now(), 0, 0)`;
console.log(sql);
let insert_query = connection.query(sql);
});
console.log("finished");
connection.end();
let responseBody = { message: "OK" };
let response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers" : "Content-Type",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET"
},
body: JSON.stringify(responseBody)
};
return response;
})
.catch(err => {
console.log('Error: ', err.message);
let responseBody = { message: "Fail" };
let response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers" : "Content-Type",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET"
},
body: JSON.stringify(responseBody)
};
return response;
});
}
First of all a forEach loop will call connection.query(sql) function multiple times then exit the loop without actually waiting for each query to finish executing so you'll end up executing random number of queries each time you run this loop instead what you want to do is use async/await await connection.query(sql) in order to wait for each query inside the loop to finish executing before exiting the loop.
Also forEach loop is not designed for asynchronous code so you'll have to change that as well and use for...of instead. And you also have to use prepared statements using ? instead of inserting values with ${variable} to prevent sql injections.
for (const post of posts) {
const sql = "INSERT INTO tbl_post(news_id, title) VALUES (?, ?)";
const values = [post.id, post.title.rendered];
console.log(mysql.format(sql,values)); // This would log query after values substitution
await connection.execute(sql, values);
}
So the final code will look something like this:
exports.handler = async (event, context) => {
try {
//Get Data From API
const res = await axios.get("https://xxx.xyz/wp-json/wp/v2/posts");
const headerDate = res.headers && res.headers.date ? res.headers.date : "no response date";
console.log("Status Code:", res.status);
console.log("Date in Response header:", headerDate);
//this should result 4 data
const posts = res.data;
for (const post of posts) {
const sql = "INSERT INTO tbl_post(news_id, title) VALUES (?, ?)";
const values = [post.id, post.title.rendered];
console.log(mysql.format(sql, values)); // This would log query after values substitution
await connection.execute(sql, values); // Execute prepares statement first then executes it.
}
console.log("finished");
await connection.end();
let responseBody = { message: "OK" };
let response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET",
},
body: JSON.stringify(responseBody),
};
return response;
} catch (err) {
console.log("Error: ", err.message);
let responseBody = { message: "Fail" };
let response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET",
},
body: JSON.stringify(responseBody),
};
return response;
}
};
As a side note consider using transactions if u want to guarantee that all queries inside the loop either succeed or fail.
Pro tip: use Promise.all() if u want to execute multiple async functions at the same time not one after the other.
I'm currently using axios and NextJS.
I currently have this code in my component:
export async function getServerSideProps(context) {
const data = await getVideo(context.query.id);
console.log('data: ', data);
// console.log('context: ', context);
console.log('context params: ', context.params);
console.log('context query: ', context.query);
if (!data) {
return { notFound: true };
}
return {
props: {
videoId: context.params.id,
videoSlug: context.params.slug,
videoContent: data
}
};
}
This getserverSideProps call the function of getVideo which looks exactly like this:
export const getVideo = (id) => async (dispatch) => {
dispatch({ type: CLEAR_VIDEO });
try {
console.log('Action file: ', id);
const res = await api.get(`/videos/${id}`);
return dispatch({
type: GET_VIDEO,
payload: res.data
});
} catch (err) {
dispatch({
type: VIDEO_ERROR,
payload: { msg: err.response?.statusText, status: err.response?.status }
});
}
};
Said function goes through my api function to make requests to backend:
import axios from 'axios';
import { LOGOUT } from '../actions/types';
import { API_URL } from '../config';
const api = axios.create({
baseURL: `${API_URL}/api/v1`,
headers: {
'Content-Type': `application/json`
}
});
/**
intercept any error responses from the api
and check if the token is no longer valid.
ie. Token has expired
logout the user if the token has expired
**/
api.interceptors.response.use(
(res) => {
res;
console.log('Res: ', res.data);
},
(err) => {
if (err?.response?.status === 401) {
typeof window !== 'undefined' &&
window.__NEXT_REDUX_WRAPPER_STORE__.dispatch({ type: LOGOUT });
}
return Promise.reject(err);
}
);
export default api;
It works great when doing POST, PUT,PATCH requests.
As you can see, I'm doing a console.log('data: ',data) but it returns [AsyncFunction (anonymous)] whenever I read the terminal; on the other hand, the front-end returns this error:
Server Error Error: Error serializing .videoContent returned from
getServerSideProps in "/videos/[id]/[slug]". Reason: function
cannot be serialized as JSON. Please only return JSON serializable
data types.
Does anyone knows how to solve this?
NOTE: I'm using react-redux, redux and next-redux-wrapper.
That is because your getVideo function returns another function. The right way to call it would be:
const data = await getVideo(context.query.id)()//<- pass in the dispatch here
But you should not use redux in the backend like that. I think you can completely remove it.
export const getVideo async (id) => {
try {
console.log('Action file: ', id);
const res = await api.get(`/videos/${id}`);
return res.data
});
} catch (err) {
return { msg: err.response?.statusText, status: err.response?.status }
}
};
// call
const data = await getVideo(context.query.id)
When retrieving a complex JSON object from chrome.storage.local the object is breaking.
mock.json
{
"ThingOne" : [
"a",
"b"
],
"ThineTwo" : [
"a",
"b"
],
"People" : {
"FamilyOne" : {
"AgeOne" : "3",
"AgeTwo" : "8"
}
},
"Hats" : ["blue", "red", "green"]
}
and I am fetching this file (correctly) using
fetch('./mock.json').then(response => {
console.log(response);
return response.json();
}).then(data => {
//data == the whole json file
var data2 = JSON.stringify(data);
chrome.storage.local.set({'StoredJson': data2});
//here this is the result of this code
//console.log(data2.ThingOne[0]);
//outputs => "a"
}).catch(err => {
console.log("Error Reading data " + err);
});
waitfunction();
chrome.storage.local.get('StoredJson', function(result) {
console.log("from get ------"); //outputs below
console.log(result); //{Data: ""{\"ThingOneOne\":[\"a\",\"b\"],\...
console.log(typeof result); //object
console.log(typeof result.ThingOne);//undefined
//https://imgur.com/OF7pVQQ
});
Why is it working when I fetch the object but not when I retrieve it. I have tried storing it after JSON.stringifying it. And I have tried to use it after JSON.parsing it which returns
VM6:1 Uncaught SyntaxError: Unexpected token o in JSON at position 1
at JSON.parse ()
indicating that it is already a JS object.
I have tried using dot notation and bracket notaion it doesn't work. When I store it in the chrome console as var data = {//json here} it works. But not live. StackOverflow: Save json to chrome storage / local storage hasn't helped me. Picture of console
There are multiple problems in the code.
There's no need for JSON.stringify. Just store the data directly.
Both fetch and chrome.storage are asynchronous so your chrome.storage.local.get will run before the data is set and it won't see the correct data.
waitfunction(); won't wait for anything, it won't influence asynchronous code before it or afterwards.
chrome.storage.local.get('StoredJson', callback) reads the data into an object property named StoredJson i.e. you can read the value as result.StoredJson.
Overall, a proper modern solution is to switch to async/await:
(async () => {
try {
const data = await (await fetch('./mock.json')).json();
console.log('Fetched', data);
await writeStorage({StoredJson: data});
const {StoredJson} = await readStorage('StoredJson');
console.log('Stored', StoredJson);
} catch (err) {
console.log(err);
}
})();
function readStorage(key) {
return new Promise(resolve => {
chrome.storage.local.get(key, resolve);
});
}
function writeStorage(data) {
return new Promise(resolve => {
chrome.storage.local.set(data, resolve);
});
}
Up, running and ready for action!
When GET method is used, the below output comes but never completes loading... from POSTMAN Why?
Successfully connected to MongoDB instance!
MongoDB returned the following documents:
[ { _id: ObjectID { _bsontype: 'ObjectID', id: [Object] },
name: 'Apple',
price: 2.5 },
{ _id: ObjectID { _bsontype: 'ObjectID', id: [Object] },
name: 'Pear',
price: 3 },
{ _id: ObjectID { _bsontype: 'ObjectID', id: [Object] },
name: 'Orange',
price: 3 } ]
When POST method is used, the below error occurred. Why?
/Users/json/Dev/restful_api/api.js:21
database.insert('OrderBase', resourceName, resource, function(err, resource) {
^
TypeError: database.insert is not a function
at insertResource (/Users/json/Dev/restful_api/api.js:21:12)
at insertProduct (/Users/json/Dev/restful_api/api.js:34:3)
at IncomingMessage.<anonymous> (/Users/json/Dev/restful_api/api.js:66:9)
at emitNone (events.js:86:13)
at IncomingMessage.emit (events.js:185:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:74:11)
at process._tickCallback (internal/process/next_tick.js:98:9)
Can anyone explain? I am new to NodeJs. Thanks a lot!
var http = require('http');
var database = require('./database');
var url = require('url');
// Generic find methods (GET)
function findAllResources(resourceName, req, res) {
database.find('OrderBase', resourceName, {}, function (err, resources) {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(resources));
});
};
var findResourceById = function (resourceName, id, req, res) {
database.find('OrderBase', resourceName, {'_id': id}, function(err, resource) {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(resource));
});
};
// Generic insert/update methods (POST, PUT)
var insertResource = function (resourceName, resource, req, res) {
database.insert('OrderBase', resourceName, resource, function(err, resource) {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(resource));
});
};
// Product methods
var findAllProducts = function (req, res) {
findAllResources('Products', req, res);
};
var findProductById = function (id, req, res) {
findResourceById('Products', id, req, res);
};
var insertProduct = function (product, req, res) {
insertResource('OrderBase', 'Product', product, function (err, result) {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(result));
});
};
var server = http.createServer(function (req, res) {
// Break down the incoming URL into its components
var parsedURL = url.parse(req.url, true);
// Determine a response based on the URL
switch (parsedURL.pathname) {
case '/api/products':
if (req.method === 'GET') {
// Find and return the product with the given id
if (parsedURL.query.id) {
findProductById(id, req, res);
}
// There is no id specified, return all products
else {
findAllProducts(req, res);
}
}
else if (req.method === 'POST') {
//Extract the data stored in the POST body
var body = '';
req.on('data', function (dataChunk) {
body += dataChunk;
});
req.on('end', function () {
// Done pulling data from the POST body.
// Turn it into JSON and proceed to store it in the database.
var postJSON = JSON.parse(body);
insertProduct(postJSON, req, res);
});
}
break;
default:
res.end('You shall not pass!');
}
});
server.listen(8080);
console.log('Up, running and ready for action!');
database file
// Our primary interface for the MongoDB instance
var MongoClient = require('mongodb').MongoClient;
// Used in order to verify correct return values
var assert = require('assert');
/**
*
* #param databaseName - name of the database we are connecting to
* #param callBack - callback to execute when connection finishes
*/
var connect = function (databaseName, callback) {
// URL to the MongoDB instance we are connecting to
var url = 'mongodb://localhost:27017/' + databaseName;
// Connect to our MongoDB instance, retrieve the selected
// database, and execute a callback on it.
MongoClient.connect(url, function (error, database) {
// Make sure that no error was thrown
assert.equal(null, error);
console.log("Successfully connected to MongoDB instance!");
callback(database);
});
};
/**
* Executes the find() method of the target collection in the
* target database, optionally with a query.
* #param databaseName - name of the database
* #param collectionName - name of the collection
* #param query - optional query parameters for find()
*/
exports.find = function (databaseName, collectionName, query) {
connect(databaseName, function (database) {
// The collection we want to find documents from
var collection = database.collection(collectionName);
// Search the given collection in the given database for
// all documents which match the criteria, convert them to
// an array, and finally execute a callback on them.
collection.find(query).toArray(
// Callback method
function (err, documents) {
// Make sure nothing went wrong
assert.equal(err, null);
// Print all the documents that we found, if any
console.log("MongoDB returned the following documents:");
console.dir(documents);
// Close the database connection to free resources
database.close();
})
})
};
i have one doubt in the node js
i need to get the data from the rss feed
for that i install the rss-parser module in it
https://www.npmjs.com/package/rss-parser
let Parser = require('rss-parser');
let parser = new Parser();
(async () => {
let feed = await parser.parseURL('https://www.reddit.com/.rss');
console.log(feed.title);
feed.items.forEach(item => {
console.log(item.title + ':' + item.link)
});
})();
the code was like that
here they are using the async function
to get the data feed for one url
i have lot of urls
i need to loop it and get the feed details in single array
is there any posibility
please tell me is there any thing
i need to get the all feed url details in a single array
I tried as of now this
I tried this code
exports.getRssFeedLinks = () => {
// Setting URL and headers for request
// Return new promise
return new Promise((fulfill, reject) => {
// Do async job
let getSql = 'SELECT * FROM `news_feeds`';
//console.log(updateSql);
connection.query(getSql, (error, results, fileds) => {
if(error) {
reject(error);
}
else {
returnResult = JSON.stringify(results);
fulfill(returnResult);
}
});
})
}
exports.errHandler = function(err) {
console.log(err.message);
}
exports.getRssFeeds = (req, res) => {
let parser = new Parser();
let feedLink;
var dataPromise = this.getRssFeedLinks();
//console.log(dataPromise);
dataPromise.then(JSON.parse, this.errHandler)
.then(function(newFeeds) {
// Do one more async operation here
let feedsList = [];
if(newFeeds && newFeeds.length > 0) {
let feedLinks = [];
newFeeds.forEach(feed => {
feedLinks.push(feed.link);
});
(async () => {
let feeds = await Promise.all(feedLinks.map(parser.parseURL));
//feeds will have array of arrays, each array includes the response feed from each url
feeds = [].concat(...feeds) //if you want to flatten the array
feed.forEach(({item}) => {
console.log(item.title + ':' + item.link)
});
feeds.forEach(feed => {
console.log(feed.title);
feed.items.forEach(item => {
console.log(item.title + ':' + item.link)
});
})
})();
}
if(feedsList.length >0) {
res.send({
"success" : true,
"result" : feedsList
});
}
else {
res.send({
"success" : true,
"message" : "No Record ",
"result" : feedsList
});
}
}, this.errHandler);
}
Errors
(node:5700) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property
'options' of undefined
(node:5700) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
You can use Promise.all to send multiple request.
(async () => {
let feeds = await Promise.all(['https://www.reddit.com/.rss1', 'https://www.reddit.com/.rss2'].map(parser.parseURL));
//feeds will have array of arrays, each array includes the response feed from each url
feeds = [].concat(...feeds) //if you want to flatten the array
feed.forEach(({item}) => {
console.log(item.title + ':' + item.link)
});
//or use loop through each feed
feeds.forEach(feed => {
console.log(feed.title);
feed.items.forEach(item => {
console.log(item.title + ':' + item.link)
});
})
})();