I got this server code running
const fs = require('fs');
const express = require('express');
const app = express();
app.get('/profile/:id', function (req, res) { // A route with a parameter
res.render('profile', {
user: getUserById(req.params.id)
});
});
app.listen(8888, function () {
console.log('Server running on port 8888');
});
function getUserById(userId){
fs.readFile('./database.json', 'utf8', function (err, data) {
var json = JSON.parse(data);
var users = json.users;
return users.find(u => u.id === userId);
});
}
And when calling the route, the function getUserById gets called. In my database, I have this data
{
"users": [
{
"id": 2312,
"name": "Foo Bar",
}
]
}
so the route would be /profile/2312 for example.
req.params.id returns the value 2312.
In the loop at var currentUser = users[0]; currentUser.id will return 2312 and the parameter passed in is 2312.
But when assigning user = currentUser; the object user is null.
Do I miss a module? Is the code wrong?
user object is null because you are returning it before your code actually reads the file.
fs.readFile('./database.json', 'utf8', function (err, data) { }
fs.readFile is asynchronous, so in order to return correct value you have to move the return statement inside fs.readFile block.
Also since getUserById is calling an asynchronous function, you have to call res.render after 'getuserById' finishes executing.
const fs = require('fs');
const express = require('express');
const app = express();
app.get('/profile/:id', getUserById);
app.listen(8888, function () {
console.log('Server running on port 8888');
});
function getUserById(req,res){ // Get a user from the database by userId
const userId = req.params.id;
fs.readFile('./database.json', 'utf8', function (err, data) {
var json = JSON.parse(data); // get the JSON object
var users = json.users; // convert the object to a user array
var match = users.find(u=>u.id.toString()===userId.toString());
//Call render after the asynchronous code finishes execution.
res.render('profile', {
user: match
});
});
}
How does Asynchronous Javascript Execution happen? and when not to use return statement?
Related
*I understand there is a lot of code here, but I've been struggling with this problem for a long time with no joy.
Node.js app with Express, MySQL database and EJS templating engine. I'm a total newbie.
I have a javaScript (getScoresData.js) file that returns data from MySQL database and contains code that creates a JavaScript object. This object contains values I need to send to front end (to create a data chart). The code returns the object to console when I run getSCoresData.js file so I know this part is working.
But, I have no idea how to properly implement this code/js file in order to send the object through my routes to the front end. I also don't know where getScoresData.js should actually sit in the project structure or how/if I should modularize the getScoresData.js file.
The structure is..
project/
app/
routes.js
config/
database.js
passport.js
models/
getScoresData.js
public/
scripts/
dataGraph.js
views/
server.js
getScoresData.js below
// db connection
var mysql = require('mysql');
var dbconfig = require('../config/database');
const connection = mysql.createConnection(dbconfig.connection);
//Sql Query
const scoreQuery = "SELECT question1, question2, question3, question4, question5, question6, question7, question8 FROM assessment_score";
//variable to hold score array
var scoreArray;
//returning the sql query
connection.query(scoreQuery, function (err, result) {
if (err) throw err;
//running getData() function
getData(result);
console.log(scoreArray);
// Slicing the array to make 8 categories
var category1 = scoreArray.slice(0,1);
var category2 = scoreArray.slice(2,3);
var category3 = scoreArray.slice(4,5);
var category4 = scoreArray.slice(6,7);
//parsing and accumlating each category score
var cat1Total = totalScore(category1);
var cat2Total = totalScore(category2);
var cat3Total = totalScore(category3);
var cat4Total = totalScore(category4);
//this is the object I want to send to client side to use in the graphData.js file
const categories = {cat1Total, cat2Total, cat3Total, cat4Total}
});
//function to turn sql result into an array of strings
function getData(result) {
Object.keys(result).forEach(function(key) {
const values = result[key];
return scoreArray = Object.values(values);
});
}
// function to parse the strings into numbers and accumulate them
function totalScore(categoryScore){
return categoryScore.reduce((accum,scoreArray) =>
{
const splitValues = scoreArray.split('/');
return {
score:accum.score + parseInt(splitValues[0]),
maxScore:accum.maxScore + parseInt(splitValues[1]),
}
},{score:0,maxScore:0}
);
}
routes.js file
I want to send the data through the /profile route so when users login they will displayed a graph of their score data on their profile.
module.exports = function(app, passport){
app.get('/', function(req, res){
res.render('index.ejs');
});
app.get('/login', function (req, res){
res.render('login.ejs', {message: req.flash('loginMessage')});
});
app.post('/login', passport.authenticate('local-login',{
successRedirect: '/profile',
failureRedirect: '/login',
failureFlash: true
}),
function(req, res){
if(req.body.remember){
req.session.cookie.maxAge = 1000 * 60 * 3;
}else{
req.session.cookie.expires = false;
}
res.redirect('/');
});
app.get('/profile', isLoggedIn, function (req, res) {
res.render('profile.ejs', {user:req.user
})
});
};
function isLoggedIn(req, res, next) {
if(req.isAuthenticated())
return next();
res.redirect('/');
});
dataGraph.js file
- where I want to use the categories object to create the graph
var ctx = document.getElementById("myChart").getContext('2d');
//Where I want to use the data sent through routes
var barTotalCategoryScores = [categories.cat1Total.score, categories.cat2Total.score, categories.cat3Total.score, categories.cat4Total.score];
var labels = ["Java & Design", "Build & Versioning"];
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: barTotalCategoryScores
}
}
});
Building a small MVC. When I'm receiving results back from my model, the variable that I'm using to send to my view is undefined if I use the "var" keyword. If I don't use the keyword the object comes through just fine. What is happening?
Controller
const homeModel = require('../models/homeModel.js');
exports.index = function(req, res){
homeModel.getAllStores(function (err, res) {
if (err) return err;
stores = res; // Works
var stores = res // Undefined
})
console.log(stores);
res.render('home', {stores: stores});
}
Here is the Model
const db = require('../db.js');
exports.getAllStores = function(done) {
db.query('select * from stores;', (err, rows) => {
if (err) return done(err);
let resultJson = JSON.stringify(rows);
resultJson = JSON.parse(resultJson);
return done(null, resultJson);
})
}
You need to move the declaration of stores to the function enclosing homeModel.getAllStores(). This is because JavaScript is function (lexically) scoped, so a variable will be scoped to the nearest enclosing function. You can read more about how variables that are declared using var work on MDN.
In Node.js, if you don't provide the var keyword before your variable then it is globally scoped to the module in which it is running, this is why console.log(stores) works when you use stores = res and not var stores = res.
To properly scope your variable using var, just move your declaration to the function being exported.
Additionally, your console.log() and res.render() calls are occurring before the callback function for homeModel.getAllStores() is executed and setting stores = res. Since res.render() and console.log() will only work as expected within the callback to homeModel.getAllStores() you can simplify index() and the callback to homeModel.getAllStores().
const homeModel = require('../models/homeModel.js')
exports.index = (req, res) => {
return homeModel.getAllStores((err, stores) => {
if (err) {
throw err
}
console.log(stores)
return res.render('home', {stores})
})
}
You could also use util.promisify() and async/await to write this a little more straightforward.
const {promisify} = require('util')
const getAllStores = promisify(require('../models/homeModel').getAllStores)
const index = async (req, res) => {
let stores
try {
stores = await getAllStores()
} catch (err) {
console.error(err)
return res.sendStatus(500)
}
return res.render('home', {stores})
}
module.exports = {index}
Here is an example with Promise.all() waiting for the results from multiple queries with a hypothetical UserModel with getAllUsers() that works identically to homeModel.getAllStores() but queries a users table.
const {promisify} = require('util')
const getAllUsers = promisify(require('../models/userModel').getAllUsers)
const getAllStores = promisify(require('../models/homeModel').getAllStores)
const index = async (req, res) => {
let queryResults
try {
queryResults = await Promise.all([getAllStores, getAllUsers])
} catch (err) {
console.error(err)
return res.sendStatus(500)
}
let [stores, users] = queryResults
return res.render('home', {stores, users})
}
module.exports = {index}
I have a form with one field that allows user to enter multiple developer id via comma delimited (ab1234,bc5678).
Once the form is submitted I perform the following tasks:
Get the the project
Loop through array of developer IDs to get their full name using mySQL
update the project using MongoDB
I'm new and sure this this is possible, The codes I have below is not working for me. Can someone please let me know if the codes below is even close.
const mongoose = require('mongoose'
const mysql = require('mysql');
// Create mySQL connection
const mySQLdb = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'root',
database : 'projects'
});
const Project = mongoose.model('project');
router.post('/developerSave', async (req, res) => {
let devList = req.body.dev_ids,
devIdArr = devList.split(','),
rData = {};
// get project
const project = await Project.findById(req.body.projectID);
mySQLdb.connect();
for(var i=0, len=devIdArr.length; i < len; i++) {
let sql = `SELECT CONCAT(first_name, ' ', last_name) as full_name FROM users WHERE id= '${devIdArr[i]}'`;
mySQLdb.query(sql, function (err, results) {
if (err) throw err;
let newDev = {
userId: devIdArr[i],
fullName: results[0].full_name
}
project.developers.unshift(newDev);
await project.save();
});
}
mySQLdb.end();
rData.success = true;
rData.msg = 'Developer was added successfully.';
res.status(200).json(rData);
});
The reason you are seeing this is because your await project.save(); is inside the callback function. Your main function will not wait for all the callbacks to complete and close the db connection. Lets look at the example below
const myCallback = (param, callback) => {
setTimeout(() => {
console.log('callback function', param);
callback();
}, 1000)
}
const myAsync = async () => {
console.log('inside async');
const result = await axios.get('http://google.com/');
for (let i = 0; i < 10; i++) {
myCallback(i, () => {
console.log('this is the actual callback function');
});
}
const result2 = await axios.get('http://bing.com/');
console.log('after second call');
}
myAsync();
The output of this is
inside async
after second call
callback function 0
this is the actual callback function
...
As you can see, the after second call is printed before the callback functions.
To solve this problem, you can wrap your callback function in a promise and resolve that once save is complete.
I have a small JSON file with this content
{
"users": [
{
"id": 1593,
"name": "Foo Bar"
}
]
}
and I want to read this content by using the filesystem module. So my application looks this
const fs = require('fs');
const express = require('express');
const app = express();
app.get('/users/:id', function (req, res) {
fs.readFile('./userDb.json', 'utf8', function (err, data) {
var json = JSON.parse(data);
var users = json.users;
console.log(users[0].id); // returns 1593
console.log(req.params.id); // returns 1593
var userObj = null;
for(var i = 0; i < users.length; i++){
var currentUser = users[i];
console.log(currentUser.id); // returns 1593
if (currentUser.id === req.params.id) { // this should be fine 1593 === 1593
userObj = currentUser;
break;
}
}
console.log(userObj); // returns undefined
res.render('users', {
user: userObj
});
});
});
app.listen(3000, function () {
console.log('Server running on port 3000');
});
The log will always return 1593, when I pass it as a parameter but when I want to render my handlebars template, the assigned object is null.
userObj is null, even when trying this code
var userObj = users.find(u => u.id === req.params.id);
but I think the database is not wrong. Where did I made a mistake =?
Pretty sure that req.params.id is a String. Try :
const userObj = users.find(u => u.id === Number(req.params.id));
When you have a doubt about it :
console.log(
`First value : ${v1} - ${typeof v1}`,
`Second value : ${v2} - ${typeof v2}`,
);
Please replace
if (currentUser.id === req.params.id)
with
if (currentUser.id == req.params.id)
Since req.params.id is a string and and currentUser.id is a number, they cannot be compared strictly .
However a non-strict comparison should work fine.
console.log(userObj); // returns undefined
is called after the readFile callback, this is an asynchronous non-blocking event!
You have to move your:
res.render('users', {
user: userObj
});
at the end of readFile function
I have an expressjs app with the following routes and middleware modules. I am trying to test the routes module using mocha, chai, http-chai and sinonjs.
The API uses mysql and in order to test the routes module, I have it all modularized so that I can stub out the mysql module.
However when I try to stub middleware/index, I am having trouble. If I try to require index normally, the module doesn't actually get stubbed. If I try to require it using require.cache[require.resolve('./../../lib/routes/middleware/index')];, it seems to stub something, but indexStub.returns(indexObj) returns an error TypeError: indexStub.returns is not a function and TypeError: indexStub.restore is not a function.
How do I stub out index.js properly in order to control the code flow and keep it from trying to connect to mysql?
routes.js
'use strict';
const express = require('express');
const router = express.Router();
const configs = require('./../config/configs');
const middleware = require('./middleware/index');
const bodyParser = require('body-parser');
const useBodyParserJson = bodyParser.json({
verify: function (req, res, buf, encoding) {
req.rawBody = buf;
}
});
const useBodyParserUrlEncoded = bodyParser.urlencoded({extended: true});
// creates a new post item and return that post in the response
router.post('/posts', useBodyParserUrlEncoded, useBodyParserJson, middleware.validatePostData, middleware.initializeConnection, middleware.saveNewPost, middleware.closeConnection, function(req, res) {
if (res.statusCode === 500) {
return res.send();
}
if (res.statusCode === 405) {
return res.send('Item already exists with slug ' + req.body.slug + '. Invalid method POST');
}
res.json(res.body).end();
});
module.exports = router;
middleware/index.js
'use strict';
const configs = require('./../../config/configs');
const database = require('./../../factories/databases').select(configs.get('STORAGE'));
const dataV = require('./../../modules/utils/data-validator');
module.exports = {
initializeConnection: database.initializeConnection, // start connection with database
closeConnection: database.closeConnection, // close connection with database
saveNewPost: database.saveNewPost, // creates and saves a new post
validatePostData: dataV.validatePostData, // validates user data
};
spec-routes.js
'use strict';
var chai = require('chai');
var chaiHttp = require('chai-http');
var sinonChai = require("sinon-chai");
var expect = chai.expect;
var sinon = require('sinon');
chai.use(sinonChai);
chai.use(chaiHttp);
var app = require('./../../app');
describe('COMPLEX ROUTES WITH MIDDLEWARE', function() {
var indexM = require.cache[require.resolve('./../../lib/routes/middleware/index')];
describe('POST - /posts', function() {
var indexStub,
indexObj;
beforeEach(function() {
indexStub = sinon.stub(indexM);
indexObj = {
'initializeConnection': function(req, res, next) {
return next();
},
'closeConnection': function(req, res, next) {
return next();
},
'validatePostData': function(req, res, next) {
return next();
}
};
});
afterEach(function() {
indexStub.restore();
});
it('should return a 500 response', function(done) {
indexObj.saveNewPost = function(req, res, next) {
res.statusCode = 500;
return next();
};
indexStub.returns(indexObj);
chai.request(app)
.post('/posts')
.send({'title': 'Hello', 'subTitle': 'World', 'slug': 'Example', 'readingTime': '2', 'published': false})
.end(function(err, res) {
expect(res).to.have.status(500);
done();
});
});
});
});
You don't use Sinon at all, as it doesn't deal with module loading at all. I see you have started doing this manually using the internal Node API's, but I suggest you do it the way we advise in the Sinon docs regarding this usecase: juse use proxyquire.
It enables you to substitute require calls to ./middleware/index.js for a mock object of your own liking (possibly made using sinon).
You would use it something like this:
var myIndex = {
initializeConnection: sinon.stub(),
closeConnection: sinon.stub(),
saveNewPost: sinon.stub()
};
var app = proxyquire('./../../app', {'./middleware/index': myIndex});