Async Nested Callbacks using Express + Request node module in a MVC structure - javascript

I'm struggling with what feels like the final step in passing some data from a model file back into a controller using Node Request.
I've successfully set up a callback from my model file which uses request to load JSON from an external source.
My controller can access this, but I think I still need some kind of nested second callback in the final step as I want the variable pageJSON to contain the JSON object and can't quite figure out how.
Think I've hit a bit of a brick wall with this and some fresh eyes on the problem would be appreciated! It feels like I'm missing something really simple at this point (I hope!)
My model file:
module.exports = function (config, callback) {
const request = require('request');
const options = {
'url' : config.urls.page,
'json' : true,
'auth': {
'user': config.auth.username,
'pass': config.auth.password
}
};
request(options, (error, response, body) => {
if (error) {
console.log(error);
}
callback(body);
});
}
My controller file:
const express = require('express');
const router = express.Router();
const app = express();
const config = require('../config');
const page = require('../models/page');
let pageJSON = page(config, (json) => {
console.log(json); // This shows the JSON structure in console
return json;
});
console.log(pageJSON); // Undefined
// Manipulate JSON and pass request view accordingly using Express

You will have to deal with json manipulation within your controller callback (or call another callback from it):
let pageJSON = page(config, (json) => {
console.log(json); // This shows the JSON structure in console
processJSON(json);
});
pageJSON is undefined because nothing is returned from your model.

Related

How do I access Request and Response object in other files Node js(Express)

Below is my project folder structure :
app
Controller
testController.js
Service
testInfoService.js
testMoreDataService.js
config
node-modules
public
index.js
routes
routes.js
Here some code :
routes.js
router.get('/config', (req, res) => {
require(controllerDirectoryPath+"/testController.js").index(req, res)
})
testController.js
const testInfoService = require('somePath/Service/testInfoService')
const index = async (req, res) =>{
console.log('Request object : ');
console.log(req);
console.log('Response object : ');
console.log(res);
//getting more info from service
testMoreInfo = await testInfoService.getMoreInformation(args)
return res.status(200).send(testMoreInfo);
}
module.exports = {index:index}
testInfoService.js
const testMoreDataService = require("somePath/testMoreDataService")
const getMoreInformation = async (args)=> {
...some code here
response = await testMoreDataService.getMoreData(args)
return response
}
module.exports = {getMoreInformation:getMoreInformation}
testMoreDataService.js
const getMoreData = async (args)=> {
**//here I want to use Request and Response object**
...some code here
return response
}
module.exports = {getMoreData:getMoreData}
Is there any way I can access req and res object in my service?
I am using node : 12.16.2 with Express.
Main file is public/index.js
Also let me know is this the correct way I am following for any application(REST-API)?
The structure is correct,
Route -> Controller -> Service
You don't have to propagate the actual req, res object all the way down to all the services.
In your controller, you would fetch the following,
Query parameter. Eg. req.query.limit
Path variable. Eg.req.param.id
Request body. Eg. req.body/req.body.name
Once you get the required information in the controller, you could construct an object or pass the relevant information to your service.
Do all the business logic in your service and return the final information to the controller.
The controller would then send this response back to the client.

Node/Express - use API JSON response to (server-side) render the app

Preamble: I'm new to web dev so maybe this might be a very basic question for you vets.
I'm using MVC architecture pattern for this basic app. I've models (MongoDB), views (Express Handlebars), and controllers (functions that take in req, res, next and returns promises (.then > JSON is returned, .catch > error is returned). I'll be routing the paths reqs to their corresponding api endpoints in the controllers.
This makes sense (right?) when I'm purely working on API calls where JSON is the res. However, I also want to call these api endpoints > get their res.json > and use that to render my HTML using Handlebars. What is the best way to accomplish this? I can create same controllers and instead of resp being JSON, I can do render ("html view", res.json). But that seems like I'm repeating same code again just to change what to do with the response (return JSON or Render the JSON).
Hope I'm making sense, if not, do let me know. Please advise.
p.s. try to ELI5 things for me. (:
Edit:
//Model Example
const Schema = require('mongoose').Schema;
const testSchema = new Schema({
testText: { type: String, required: true },
});
const Test = mongoose.model('Test', testSchema);
module.exports = Test;
//Controller Example
const model = require('../models');
module.exports = {
getAll: function(req, res, next) {
model.Test.find(req.query)
.then((testItems) => {
!testItems.length
? res.status(404).json({ message: 'No Test Item Found' })
: res.status(200).json(testItems);
})
.catch((err) => next(err));
},
};
//Route Example
const router = require('express').Router(),
controller = require('../controllers');
router.get('/', controller.getAll);
module.exports = router;
I want the endpoints to return JSON and somehow manage whether to render (if the req comes from a browser) or stay with JSON (if called from Postman or an API web URL for example) without repeating the code. I'm trying to not create two endpoitns with 99% of the code being the same, the only difference being .then > res.status(200).json(testItems); vs .then > res.status(200).render('testPage', { testItems}).
For postman you could check the existence of postman-token in req.headers, then you could render accordingly, something like this:
req.headers['postman-token'] ? res.json({ /* json */ }) : render('view', {/ * json */});
If you want to go with checking postman token then you can use similar to method1.
if you want to check with query params in this case you can get json response or html even from browser for future use also and is not dependent on postman then use similar to method2 of the following example.
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
const port = 5000
app.get('/method1', (req, res) => {
const isJSONResp = req.headers['postman-token']
const resp = { status: "hello" }
if (isJSONResp) {
res.json(resp)
} else {
res.render('some.html', resp)
}
})
app.get('/method2', (req, res) => {
const params = req.params
const resp = { status: "hello" }
if (params.resp === 'json') {
res.json(resp)
} else {
res.render('some.html', resp)
}
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))

Is it possible to call module.exports on demand in Node?

I have an app where I call an API to get headlines and digest that JSON and send the data back to the client via sockets. I have modularized all my code and used different files to stay organized. I have my index.js file, where I merely call the functions to grab to data from the API and newsDataFetcher.js is where I make the request to the API for the data using the request module. My issue is that since the request I am making is asynchronous, the data doesn't get passed to the main server file and therefore the socket doesn't send any data. After the request to the API finishes, I put the data into an array and that array is in the module.exports of my newsDataFetcher.js file, along with the function that makes the actual request. So since the array is in the module.exports, an empty array is passed when I require the newsDataFetcher.js file in the index.js file. So, that brings me to the question: is there anyway to call module.exports "on demand" so that it doesn't pass an empty array and it only passes the array when the data is finished downloading from the API?
index.js :
var newsDataFetcher = require('./newsDataFetcher');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var news = []; //array for news headlines and abstracts
app.set('view engine', 'ejs');
app.use(express.static(__dirname + '/views/routes/'));
server.listen(8888);
console.log("Running server on http://localhost:8888 ....");
newsDataFetcher.getNewsData();
news = newsDataFetcher.news;
setInterval(function () {
newsDataFetcher.getNewsData();
news = newsDataFetcher.news;
}, 6000000);
setInterval(function () {
sendAllData()
}, 1000);
function sendAllData() {
io.sockets.emit('news', news);
}
newsDataFetcher.js file:
var request = require('request');
var nyTimesApi = "http://api.nytimes.com/svc/topstories/v1/home.json?api-key=" + nyTimesApiKey_DEV;
var news = []; //array for news headlines and abstracts
function getNewsData() {
request({
url: nyTimesApi,
json: true
}, function (err, res, body) {
news = body.results;
});
}
module.exports = {
getNewsData: getNewsData,
news: news
}
You can do some like this:
// newsDataFetcher.js
module.exports = {
getNewsData: getNewsData,
news: function() {
return news;
}
}
// index.js
io.sockets.emit('news', newsDataFetcher.news());
You can use singleton pattern to achieve this.
class NewsDataFetcher {
constructor() {
this.news = [];
}
getNewsData() {
const self = this;
request({
url: nyTimesApi,
json: true
}, function (err, res, body) {
self.news = body.results;
});
}
}
module.exports = new NewsDataFetcher();
In the main page.
var newsDataFetcher = require('./newsDataFetcher');
newsDataFetcher.getNewsData(); // to fetch new data;
// To get news data
console.log(newsDataFetcher.news); // will always refer to one instance of news since you are using singleton.

How to get more than one independent response data in Express js app.get callback

What is the best practice to send two independed MongoDB results in Express application via HTTP Method?
Here is a short example which makes it clear:
//app.js
var express = require('express');
var app = express();
var testController = require('./controllers/test');
app.get('/test', testController.getCounts);
...
Following getCounts() function wouldn't work because I can't send the response twice.
///controllers/test
exports.getCounts = function(req,res) {
Object1.count({},function(err,count){
res.send({count:count});
});
Object2.count({},function(err,count){
res.send({count:count});
});
};
Anyway, I would like to have those two counts in one response object.
Should I call Object2.count in the callback of Object1 even if they are not dependent to each other?
Or should I re-design it somehow else?
Thank you!
You should use Promise to achieve this task :
function getCount(obj) {
return new Promise(function (resolve, reject) {
obj.count({}, function(err,count) {
if(err) reject();
else resolve(count);
});
});
}
With Promise.all you can trigger the two request and retrieve the results in order to add it to the response
exports.getCounts = function(req,res) {
Promise.all([getCount(Object1), getCount(Object2)])
.then(function success(result) {
res.send({'count1':result[0], 'count2':result[1]});
});
});
When you call res.send you will end the response for the request. You could instead use res.write, which will send a chunk to the client, and when done call res.end;
Example:
app.get('/endpoint', function(req, res) {
res.write('Hello');
res.write('World');
res.end();
});
However, it seems like you are trying to send json back to the client which raises and problem: writing to object separately will not be valid json.
Example:
app.get('/endpoint', function(req, res) {
res.write({foo:'bar'});
res.write({hello:'world'});
res.end();
});
The response body will now be: {foo:'bar'}{hello:'world'} which is not valid json.
There will also be a race condition between the two db queries, which means that you are not certain about the order of the data in the response.
Suggestion:
exports.getCounts = function(req,res) {
var output = {};
Object1.count({},function(err,count){
output.count1 = count;
Object2.count({},function(err,count){
output.count2 = count;
res.send(output);
});
});
};
//Response body
{
count1: [value],
count2: [value]
}

Passing data from model to router in node js

I am trying to pass some data from my db to the router which then passes the data to the view.
My model code :
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '',
database: 'test'
});
var result; // empty var which should later be filled with the querys result
connection.connect();
var query = connection.query('SELECT * FROM users', function(err, res, fields) {
if (err) throw err;
result = res; // overwrite result with the querys result
console.log(res); // This prints out everything I need
});
module.exports = {
data: result // should contain the query result (= 2 objects in this case)
}
Now to my route file :
var express = require('express');
var router = express.Router();
var Users = require('../models/users');
console.log(Users.data);
/* GET home page. */
router.get('/users', function(req, res) {
res.render('api', { data: Users.data, title: "Test API Output" });
});
module.exports = router;
When I console.log Users or Users.data I get undefined. I don't really get why this is the case. How else am I supposed to pass data along the files.
All help is gladly read :) Thank you.
module.exports are being evaluated the second you require and variables are not passed by reference in this case.
What that means for your code is the following:
var result; // result is "undefined" because it does not contain a value here
// You are doing your DB queries here...
module.exports = {
data: result // ...and because the query has not finished here yet, result
// is still undefined.
// This is also a good example of a so called "race condition", because there is a
// slight (improbable) chance that the query might have already finished.
// Hence, it could happen that sometimes result is already filled.
}
When you now require the above file in another file of your code, the above is being evaluated and saved straight away (result is undefined at that point in time, hence it is also undefined when it exports).
Your query is being executed and written into the result variable, but at that point in time you can not modify the exported variable anymore – because it is it's own variable and not merely a reference to result).
What you could do is the following:
function getData(callback) {
connection.query('SELECT * FROM users', function(err, res, fields) {
callback(err, res);
});
}
module.exports = {
getData: getData
}
and then in your other file:
var Users = require('../models/users');
Users.getData(function(err, result) {
// TODO: Error handling.
console.log(result);
});
That's exactly why it's so easy with JavaScript to end up in callback hell, because of it's asynchronous nature.
The above is the exact same situation as if you, f.e., want to get some data via AJAX from a server and then fill tables with it. When you start creating the table before you have the data (so the AJAX request is not yet complete), you end up with an empty table. What could do is:
you create a variable that holds your data and
a function that creates the table
when you then ask the server for the data (via AJAX) you wait until you get the data (completion callback) and only then you start creating the table: filling your variable and calling the function to fill the table with the data.
Server-Side JavaScript is the same as client-side. Never forget this.
As a little homework for the reader: the way to get out of callback hell is by reading up on promises – a pattern/architecture which reduces indents and saves lots of headaches :)
(update: Lucas' answer is basically telling the same thing as I did)
(update 2: wrong way of handling err)
I suggest realize the consult in the route file, some like this:
var express = require('express');
var router = express.Router();
var Users = require('../models/users');
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '',
database: 'test'
});
var result; // empty var which should later be filled with the querys result
connection.connect();
/* GET home page. */
router.get('/users', function(req, res) {
var query = connection.query('SELECT * FROM users', function(err, res, fields) {
if (err) throw err;
result = res; // overwrite result with the querys result
res.render('api', { data: res.data, title: "Test API Output" });
});
});
module.exports = router;
But you can configure the connection with database in another file, in libs/mysql_connect.js.
The undefined is caused because the response of connection.query don't works out of the connection.query.
If you really want the query to run only once and then just re-use the already queried data, I think you are after something like this for your model:
...
var data;
var mymodel = {};
...
mymodel.getData = function(callback) {
if(data) {
callback(data);
} else {
db.query('select * from users', function(err,res,fields) {
// error checking and such
data = res;
callback(data);
});
}
}
module.exports = mymodel
In your router, you'd then use it like this:
...
router.get('/users', function(req, res) {
Users.getData(function(mydata) {
res.render('api', { data: mydata, title: "Test API Output" });
});
});
The first time you call getData, you'll get a fresh result, and on subsequent calls you get the cached result.
While you could expose data in mymodel directly, and only use the callback in case it is still undefined, that'd make your code in the router more convulated.

Categories

Resources