[Tutorial] How to Use MySQL or MariaDB with Node.js and Express

I guess what I asking is: does the handlebars template get populated server side or client side?
That is what I meant by missing hbs… “missing” would be the template variable.

Redis sounds awesome.
I would love a tutorial on how to deploy this stack and use redis. I am probably at least a week from doing that though.

About the partials, I agree. I tried adding them to the view controller files, but you have to change the directory and then you also have to keep adding them. Having the partials listed in the server.js (app.js) keeps them centralized and you only write them one time! :slight_smile:

Oh, someone also mentioned helper function to me. I guess those would “help” the controller files change data before being displayed. Are those usually kept in a sub-folder of the controller?

Also also, I changed the database folder to the “models” name as I understand that is the roll of the database in the Model/View/Controller programming structure.

Handlebars can run on the server or the client. In the Express app, it only runs on the server. In this forum it runs on the client.

I’ll post those soon.

You can make two more directories:

  • helpers
  • middleware

Helpers could contain things like reusable functions for converting measurements.

Middleware probably comes later.

You can put all those functions like getMonsterById in a ./models/monster.js file. You can put the database connection (pool) in something like a db.js file that each model file can import. I haven’t used this package on my own sites, but I think that will work.

Ah yes, the db.js like in this guide works great!

I will look into adding a helpers folder.

Look forward to the tutorials!
Thanks!

1 Like

Small note, if you want to get URL parameters from a URL as such
nutrition-facts.php?food=174682

Then you can use req.query
as in
const foodId = req.query.food;

The router query doesn’t need to define the parameters
router.get("/nutrition-facts.php", ctrl.nutritionFacts);

Sample usage in a controller

function nutritionFacts(req, res) {
    const foodId = req.query.food;
    console.log(foodId);
    db.getDataById(foodId, foodId, foodId)
        .then(data => {

            //Calculations and logic go here

            // console.log("data", data[0]);
            // const {fdc_id,name3} = data[0];
            // console.log(fdc_id);
            const foodData = data[0]; // optionally edit data[0] here.

res.render("./tools/nutrition-facts.hbs", {
    custom: "Custom Title",
    foodData: foodData, // now foodData.fdc_id, etc. are available in the template
});
        })
        .catch(err => {
            // if you want anything to happen when there is an error, put it here

            // this gets sent back if there is an error
            res.status(500).json(err);
        });
    // Don't put anything down here.
}```

If you want to keep the URLs exactly the same, you can do this with req.query:

/nutrition-facts.php?food=174682

If you use req.params, then you can make URLs like this…

/nutrition-facts/food/174682

or if you have something like this…

nutrition-facts.php?food=174682?other-thing=abc

which creates duplicate content at…

nutrition-facts.php?other-thing=abc?food=174682

req.params would be able to redirect both versions to a single canonical version:

nutrition-facts/food/174682/other-thing/abc

(That’s actually an example of where middleware could be used. It could check for query parameters on the request and then do redirects to canonical locations.)

1 Like

Hmm I think I want to go for the better req.params link structure, but then I will need a way to forward the legacy links that use req.query.

For now I think it is simpler to keep the existing structure, then create a redirect to the new structure later…

1 Like

I added to my comment above. I was mentioning that middleware could be used to do the redirects. The middleware could be inserted before the routers.

// something like this (untested)
app.use((req, res, next) => {
    const { food, otherQueryString } = req.query;

    // if the query strings are there, redirect to the canonical version
    if (food && otherQueryString) {
        res.redirect(301, `/food/${food}/${otherQueryString}`);
    }

    // If the query strings weren't found, then go to the
    // next middleware (router)
    next();
});

It might be simpler to keep it as-is, but also wouldn’t be that difficult to do the redirects. :slight_smile:

1 Like

OK, so I would
router.get("/nutrition-facts.php", ctrl.nutritionFacts);

But instead of ctrl.nutritionFacts I would call the redirect function above?

And then that redirect function would call
router.get("/nutrition-facts/", ctrl.nutritionFacts); ?

Hmm, I am going to try re-create things and then clean up stuff like that. Otherwise… :face_with_head_bandage: :wink:

Thanks for letting me know though. It is good to know that a redirect will not be too tough.

Testing would be useful there. I can add a tutorial on testing that with Cypress at some point.

1 Like

Maybe something like this?

const ctrl = require("../controllers/nutritionFactsController");

// This could be your main nutrition facts page
router.get("/nutrition-facts", ctrl.index);

// This template would handle the "detail view" for each specific food (by ID)
// `:foodId` would be available in the controller as `req.params.foodId`.
router.get("/nutrition-facts/:foodId", ctrl.detail);

Then in the controller:

// not exactly this, but just illustrating an idea
const Food = require("../models/food");

function list(req, res) {
    res.render("nutrition-facts/list", { title: "Nutrition Facts" });
}

function detail(req, res) {
    const foodId = req.params.foodId;
    Food.getFoodById(foodId)
        .then(data => {
            res.render("nutrition-facts/detail", { title: data.foodName });
        });
}

module.exports = {
    list,
    detail,
};

If you want me to take a look at anything over screensharing, send a chat message any time.

1 Like

Hmm, looks about right.

Right now though, I am more interested in using helper functions.

I am going to need to process the data that comes in from the db before sending it to the view.

Any good examples? I will also search around.

You can put reusable, miscellaneous functions in a directory named helpers.

Example:

helpers/conversions.js

function ouncesToQuarts(oz) {
    return oz * 0.03125;
}

module.exports = {
    ouncesToQuarts,
};

Then in another file that wants to use that function:

// require the conversions module
const conversions = require("../helpers/conversions");

// use the module's functions
conversions.ouncesToQuarts(100);

Is that the kind of example you’re looking for?

1 Like

I think that is it exactly! Thanks!
I am going to try it and will report back!

Should the helpers folder be a subfolder of controllers? I guess so…or maybe it doesn’t matter

The helpers directory wouldn’t have any controller files in it — I’d keep all of the controllers in controllers. But if you have functions that are used in multiple controllers, then you could put those in helpers and import them into every file that needs to use them.

Controllers are the functions that take request and response. If a function has those two parameters, it should probable go in either controllers or middleware. If the function doesn’t have those two parameters, then helpers might be a place for it.

1 Like

Got it, thanks! :slight_smile:

I have a question now wondering how you can query MySQL when you will have to make multiple parameters.

For example a user may have 2-30 foods in a recipe, so I will have to query the table for each food.
What is the best way to do this with Node?

From what I understand I should put all parameters in an array and loop through it and then use promise.all

This code from stack overflow looks promising

var promises = [];

array.forEach(function(element) {
    promises.push(
        developer.getResources(element)
            .then((data) = > {
                name = data.items[0];
                return developer.getResourceContent(element, file);
            })
            .then((response) = > {
                fileContent = atob(response.content);
                self.files.push({
                    fileName: fileName,
                    fileType: fileType,
                    content: fileContent
                });
            }).catch ((error) = > {
                console.log('Error: ', error);
            })
    );
});

Promise.all(promises).then(() => 
    self.resultingFunction(self.files)
);

And then my friend also shared one with mapping

// map array of values to a query
const mapValues = async (queryText, array) => {
  const client = await pool.connect();
  const results = [];
  const queryDB = async value => {
    try {
      const { rows } = await pool.query(queryText, value);
      if (rows.length > 0) {
        results.push(rows[0]);
      }
    } catch (error) {
      client.release();
      throw error;
    }
  };
  await Promise.all(array.map(value => queryDB(value)));
  client.release();
  return results;
};

But I am having trouble getting it to work. What is the best solution to this given this kind of routing with node and express?

Can you do something like this in one query?

-- this gets you foods #133, #139, and #140
SELECT field_1, field_2, field_5
FROM foods
WHERE food_id IN (133, 139, 140);

Here’s an example in Node using the monsters code from the tutorial:

function getAllMonsters(limit = 100) {
    return new Promise((resolve, reject) => {
        // Use question marks for each placeholder.
        const sql = `SELECT * FROM monsters WHERE id IN (?) LIMIT ?`;

        // The array here has one item to fill each question mark. So
        // `[1, 3]` fills the first question mark and `limit` fills the
        // second question mark.
        pool.query(sql, [[1, 3], limit], function (err, results, fields) {
            if (err) {
                return reject(err);
            }

            return resolve(results);
        });
    });
}

To pass in the food IDs as an array, you could probably do something like this:

// Pass the `foodIds` in to the function here. You can remove `limit` if
// you don't need it.
function getSomeFoods(foodIds, limit = 100) {
    return new Promise((resolve, reject) => {
        // Use question marks for each placeholder.
        const sql = `SELECT * FROM foods WHERE id IN (?) LIMIT ?`;

        // Now, `foodIds` is an array that populates the first question
        // mark.
        pool.query(sql, [foodIds, limit], function (err, results, fields) {
            if (err) {
                return reject(err);
            }

            return resolve(results);
        });
    });
}

Edit: here is an example of passing in multiple food IDs by URL. Your code will probably be different, but this shows the general idea.

function getSomeFoods(ids, limit = 100) {
    return new Promise((resolve, reject) => {
        const sql = `SELECT * FROM foods WHERE food_id IN (?) LIMIT ?`;
        pool.query(sql, [ids, limit], function (err, results, fields) {
            if (err) {
                return reject(err);
            }

            return resolve(results);
        });
    });
}



// The URL would be something like this, with comma-separated IDs:
// http://localhost:3333/api/foods/1,2,10,25
app.get("/api/foods/:ids", (req, res) => {

    // Extract the food IDs from the params into an array
    const ids = req.params.ids.split(",");

    // Pass the IDs into the function
    db.getSomeFoods(ids)
        .then(data => res.json(data))
        .catch(err => res.status(500).json(err));
});

Someone else here might have a better idea. :slight_smile:

1 Like

Great suggestion. To using the in statement is way faster. I just clocked 200 ids with php.

loop-vs-in-statement

100ms vs 4ms… :smiley: No brainer.
I will let you know how it works out.

1 Like