I am following a nodeJS course and I have some doubts about how exactly works the flow of this NodeJS MVC application (I came from Java and JavaScript is not yet my coup of tea).
I will put here the code trying to explain what I can understand in order to check if my reasoning are correct and to expose my doubts about how to it works.
First of all I have a routes file containing some routes:
const path = require('path');
const express = require('express');
const shopController = require('../controllers/shop');
const router = express.Router();
router.get('/', shopController.getIndex);
router.get('/products', shopController.getProducts);
.................................................................
.................................................................
.................................................................
module.exports = router;
Considering this specific route:
router.get('/products', shopController.getProducts);
It is handling GET HTTP request toward the /products path by the getProducts() method defined into the ../controllers/shop' file (implementing my controller) that contains the middleware functions. It contains something like this:
const Product = require('../models/product');
const Cart = require('../models/cart');
exports.getProducts = (req, res, next) => {
Product.fetchAll(products => {
res.render('shop/product-list', {
prods: products,
pageTitle: 'All Products',
path: '/products'
});
});
};
....................................................
....................................................
....................................................
OTHER MIDDLEWARE FUNCTIONS
....................................................
....................................................
....................................................
And here I have my first doubts about how it works because it is using some functional programming style.
Basically my getProducts() middleware functions is calling the fetchAll() static method (that will retrieve all the products) defined on the Product model object defined into the ../models/product file. This is the code of this file:
/**
* Model related to a product
*/
const fs = require('fs');
const path = require('path');
const Cart = require('./cart');
const p = path.join(
path.dirname(process.mainModule.filename),
'data',
'products.json'
);
const getProductsFromFile = cb => {
fs.readFile(p, (err, fileContent) => {
if (err) {
cb([]);
} else {
cb(JSON.parse(fileContent));
}
});
};
/**
* Represents a product with its own fields and functionalities
*/
module.exports = class Product {
constructor(id, title, imageUrl, description, price) {
this.id = id;
this.title = title;
this.imageUrl = imageUrl;
this.description = description;
this.price = price;
}
save() {
getProductsFromFile(products => {
if (this.id) {
const existingProductIndex = products.findIndex(
prod => prod.id === this.id
);
const updatedProducts = [...products];
updatedProducts[existingProductIndex] = this;
fs.writeFile(p, JSON.stringify(updatedProducts), err => {
console.log(err);
});
} else {
this.id = Math.random().toString();
products.push(this);
fs.writeFile(p, JSON.stringify(products), err => {
console.log(err);
});
}
});
}
static deleteById(id) {
getProductsFromFile(products => {
const product = products.find(prod => prod.id === id);
const updatedProducts = products.filter(prod => prod.id !== id);
fs.writeFile(p, JSON.stringify(updatedProducts), err => {
if (!err) {
Cart.deleteProduct(id, product.price);
}
});
});
}
static fetchAll(cb) {
getProductsFromFile(cb);
}
static findById(id, cb) {
getProductsFromFile(products => {
const product = products.find(p => p.id === id);
cb(product);
});
}
};
So the fetchAll() method is:
static fetchAll(cb) {
getProductsFromFile(cb);
}
and here the first doubt: what exactly is the cb input parameter. My idea is that this is the arrow function defined into my gateway function, this one:
products => {
res.render('shop/product-list', {
prods: products,
pageTitle: 'All Products',
path: '/products'
});
So basically this fetchAll(cb) function is calling the getProductsFromFile(cb) static function passing to it the arrow function that at the end will render my view.
This is my getProductsFromFile(cb) function:
const getProductsFromFile = cb => {
fs.readFile(p, (err, fileContent) => {
if (err) {
cb([]);
} else {
cb(JSON.parse(fileContent));
}
});
};
it is clear for me that this function is reading the content of a file (containing my products), it is a simple file contaionin a JSON aray like this:
[{"id":"123245","title":"A Book","imageUrl":"https://www.publicdomainpictures.net/pictures/10000/velka/1-1210009435EGmE.jpg","description":"This is an awesome book!","price":"19"},{"id":"0.43641694587386115","title":"test","imageUrl":"https://www.connetweb.com/wp-content/uploads/2021/06/canstockphoto22402523-arcos-creator.com_-1024x1024-1-560x560.jpg","description":"test test test !!!","price":"33"}]
So the meaining is that after that the file is correctly readed it is executed the cb "pointer" that points to this arrow function by cb(JSON.parse(fileContent));:
products => {
res.render('shop/product-list', {
prods: products,
pageTitle: 'All Products',
path: '/products'
});
where JSON.parse(fileContent) is the products of my arrow function (that should represents the input parameters of this function. So this is performed and my view is rendered passing to it the pageTitle and the products list into my view.
Is it my reasoning correct? My doubt is: why all this nesting of function? My idea is that since I am reading data from a file on my filesystem I have to ensure that the renderd function is called as last operation in order to avoid that products is empty.
Waiting for your feedback
I think you are reasoning good.
Render function is called after products load but
you are experiencing a bit of "call back hell".
My suggestion is to try to refactor the code with Promises and async/await instead of using old style callbacks.
Other suggestion, replace outdated requires with standard ES6 modules.