2

Goal:

I am aiming to teach myself how to use Node JS, MySQL and express.

I'm struggling to understand where to place my code for loading MySQL data into HTML.

Let me show you the whole code.

app.js

var express = require('express');

var mysql = require('mysql');
var dotenv = require('dotenv');
var path = require('path');
var cookieParser = require('cookie-parser');

dotenv.config({path: './.env'});


var app = express();

// Connection to MySQL
var db = mysql.createConnection({
  host: process.env.DATABASE_HOST,
  user: process.env.DATABASE_USER,
  password:  process.env.DATABASE_PASSWORD,
  database: process.env.DATABASE
});

db.connect(function(error) {
  if(error) {
    console.log(error);
  }
  else{
    console.log("Connected");
  }
});

// Parse URL-Encoded bodies
app.use(express.urlencoded({extended: false}));
// Parse JSON bodies
app.use(express.json());
// Initialize a cookie
app.use(cookieParser());

// View engine to control HTML
app.set('view engine', 'hbs');

// Public dir
var publicDir = path.join(__dirname, './public');
app.use(express.static(publicDir));

// Define routes
app.use('/', require('./routes/pages'));
app.use('/auth', require('./routes/auth'));

app.listen(3000, function() {
  console.log("Server is running on port 3000");
});

routes/pages.js

var express = require('express');
var authController = require('../controllers/auth');

var router = express.Router();

// Home
router.get("/", authController.isLoggedIn, function(req,res) {
  res.render("index", {
    user: req.user
  });
});

// Register
router.get("/register", function(req, res) {
  res.render("register");
});

// Login
router.get("/login", function(req, res) {
  res.render("login");
});

// Profile
router.get('/profile', authController.isLoggedIn, function(req, res) {
  if(req.user) {
    res.render('profile', {
      user: req.user
    });
  }
  else {
    res.redirect('login');
  }

});

// Forum
router.get('/forums', authController.isLoggedIn, function(req, res) {
  if(req.user) {
    res.render('forums');
  } else {
    res.redirect('login');
  }
});

// English Division //

// Premier League
router.get('/Leagues/EnglishDivision', authController.isLoggedIn,  function(req, res) {
  if(req.user) {
    res.render('PremierLeague');

  } else {
    res.redirect('../../login');
  }
});


module.exports = router;

routes/auth.js

var express = require('express');
var authController = require('../controllers/auth');

var router = express.Router();

// Register
router.post("/register", authController.register);

// Login
router.post("/login", authController.login);

// Logout
router.get('/logout', authController.logout);

module.exports = router;

controllers/auth.js

var mysql = require('mysql');
var jwt = require('jsonwebtoken');
var bcrypt = require('bcryptjs');
var {promisify} = require('util');


// Connection to MySQL
var db = mysql.createConnection({
  host: process.env.DATABASE_HOST,
  user: process.env.DATABASE_USER,
  password:  process.env.DATABASE_PASSWORD,
  database: process.env.DATABASE
});

// Register function
exports.register = function(req, res) {
  console.log(req.body);

  var {name, email, password, passwordConfirm} = req.body;

  db.query("SELECT email FROM users WHERE email = ?", [email], function(error, result) {
    if(error){
      console.log(error);
    }

    if(result.length > 0) {
      return res.render('register', {
        message: 'That email is already in use'
      })
    } else if(password !== passwordConfirm) {
      return res.render('register', {
        message: 'Passwords do not match'
      });
    }

    let hashedPassword = bcrypt.hashSync(password, 8);
    console.log(hashedPassword);

    // Insert user details into MySQL
    db.query('INSERT INTO users set ?', {name: name, email: email, password: hashedPassword, dateJoined: new Date()}, function(error, result) {
      if(error) {
        console.log(error);
      } else {
        console.log(result);
        return res.render('register', {
          message: 'User registered'
        });
      }
    });
  });
}


// Login function

exports.login = function(req, res) {
  try {

    var {email, password} = req.body;

    if(!email || !password) {
      return res.status(400).render('login', {
        message: 'Please provide an email and password'
      });
    }

    db.query('SELECT * FROM users WHERE email = ?', [email], async function(error, result) {

      console.log(result);

      if(!result.length > 0 || !(await bcrypt.compare(password, result[0].password))) {
        res.status(401).render('login', {
          message: 'The email or password is incorrect'
        });
      }


      else {

        var id = result[0].id;

        // Create a token
        var token = jwt.sign({id}, process.env.JWT_SECRET, {
          expiresIn: process.env.JWT_EXPIRES_IN
        });

        console.log("The token is " + token);

        // Create a cookie
        var cookieOptions = {
          expires: new Date(
            Date.now() + process.env.JWT_COOKIE_EXPIRES * 24 * 60 * 60 * 1000
          ),
          httpOnly: true
        }

        // Set up a cookie
        res.cookie('jwt', token, cookieOptions);
        res.status(200).redirect("/");

      }
    });


  } catch (error) {
    console.log(error);
  }
}

// Check if logged in
exports.isLoggedIn = async function(req, res, next) {
  console.log(req.cookies);
  if(req.cookies.jwt){
    try {
      // Verify the token
      var decoded = await promisify(jwt.verify)(req.cookies.jwt, process.env.JWT_SECRET);

      console.log(decoded);

      // Check if user exist
      db.query("SELECT id, name, email, password, date_format(datejoined, '%d/%m/%Y') as dateJoined FROM users WHERE id = ?", [decoded.id], function(error, result) {
        console.log(result);

        // If no result
        if(!result) {
          return next();
        }

        req.user = result[0];
        return next();

      });
    }
  catch (e) {
    console.log(e);
    return next();
  }
} else{
    next();
  }
}

// Logout function
exports.logout = async function(req, res) {
  res.clearCookie('jwt');
  res.status(200).redirect('/');
}

Question

In my .hbs file called PremierLeague I'd like to load MySQL data in HTML format. Where in the code below I need to start?

Desired goal:

This is when the user clicks into view premier league

enter image description here

Foreach record in MySQL I'd like to add a new card for each record. I know how to use HandleBars {{some.data}}.

  • I just don't get where I code the query?
  • Does it needs to be in a controller or can it be in in the router.get(...?
  • Also how do I use {{#foreach}} correctly ?
Eduards
  • 1,734
  • 2
  • 12
  • 37

2 Answers2

3

You don't need any other specific controller, the right place to code the query is actually the route itself.

But before entering the core of your question, let's talk a while about your code.

I can see you are performing connection to database more than once, you could add database dedicated controller, something like:

controllers/db.js

var mysql = require('mysql');
var dotenv = require('dotenv');

dotenv.config({path: './.env'});

// Connection to MySQL
var db = mysql.createConnection({
  host:     process.env.DATABASE_HOST,
  user:     process.env.DATABASE_USER,
  password: process.env.DATABASE_PASSWORD,
  database: process.env.DATABASE
});

function connect(done) {
  db.connect(done);
}

module.exports = { db: db, connect: connect };

this let you access to the database instance from every file with just one line:

var db = require('./controllers/db').db;

than you could use the connect function in your app:

app.js

var express = require('express');
var db = require(./controllers/db);
var path = require('path');
var cookieParser = require('cookie-parser');

// set up your server
var app = express();

// Parse URL-Encoded bodies
app.use(express.urlencoded({extended: false}));
// Parse JSON bodies
app.use(express.json());
// Initialize a cookie
app.use(cookieParser());

// View engine to control HTML
app.set('view engine', 'hbs');

// Public dir
var publicDir = path.join(__dirname, './public');
app.use(express.static(publicDir));

// Define routes
app.use('/', require('./routes/pages'));
app.use('/auth', require('./routes/auth'));

// finally run your server only if you can connect to the database
db.connect(function(error) {
  if(error) return console.log("Error connecting to the database:", error);

  app.listen(3000, function() {
    console.log("Server is running on port 3000");
  });
});

you could also simplify you controllers/auth.js removing database connection stuff and using only the line to require your database controller.

Finally you can code your query:

routes/pages.js

var express = require('express');
var authController = require('../controllers/auth');
var db = require('../controllers/db').db;

var router = express.Router();

// Omissis... other routes

// Premier League
router.get('/Leagues/EnglishDivision', authController.isLoggedIn, function(req, res) {
  // a good practice is first to handle possible exit cases to reduce nesting levels
  if(! req.user) return res.redirect('../../login');

  // this is actually the right place to perform queries
  db.query('SELECT ...', [...], function(error, results) {
    // once again first possible exit cases
    if(error) return res.status(500).end(error.message)

    res.render('PremierLeague', { results: results });
  });
});

module.exports = router;

Last in your PremierLeague.hbs file you can handle the results in a #foreach directive.

Daniele Ricci
  • 15,422
  • 1
  • 27
  • 55
  • Hi, I appreciate your answer! Regarding the database connection from the controller. I placed `var db = require('../controllers/db').db;` in `app.js`. It comes with an error `Error: Cannot find module '/controllers/db'`. The structure is as follows. `app.js` and another folder called `controllers` on same level as `app.js`. – Eduards Jul 14 '20 at 10:44
  • 1
    Just check the path @LV98 ;) `require('./controllers/db')` – Daniele Ricci Jul 14 '20 at 11:02
  • Really like that idea of setting up the Database in the controller. Thanks for that. Gonna keep reading your other comments now :D – Eduards Jul 14 '20 at 11:09
  • Can you show an example of handling `results` in a `#foreach`? I have tried the following: https://stackoverflow.com/a/44715431/12485722 – Eduards Jul 14 '20 at 11:19
  • You can check the answers to [this question](https://stackoverflow.com/questions/22696886/how-to-iterate-over-array-of-objects-in-handlebars) @LV98 – Daniele Ricci Jul 14 '20 at 11:25
  • Thanks - would you recommend using `ejs` instead of `.hbs` for my view engine instead? – Eduards Jul 14 '20 at 11:30
  • Sorry @LV98 but I'm not strong enough on this to give you a significant suggestion; I usually write my FEs with React, I do not use templates. – Daniele Ricci Jul 14 '20 at 11:36
  • I am also passing data from `isLoggedIn` controller `req.user: user` similar to my `router.get('/')`. Where would I code `req.user`? I believe it should be underneath `results: results`? like so `{results: results, req.user: user}`? – Eduards Jul 14 '20 at 12:54
  • 1
    If you want to add the `user` data to the template for render something (eg. the name) @LV98 you have to the pass it to the template as `{ result: result, user: req.user }` – Daniele Ricci Jul 14 '20 at 13:46
  • Hi @LV98 how is going with your queries? – Daniele Ricci Jul 19 '20 at 17:32
0

Just pass your data when you render the view

    router.get('/Leagues/EnglishDivision', authController.isLoggedIn,  function(req, res) {
      if(req.user) {
       connection.query('SELECT * FROM EnglishDivision',function (err,results) {
        if (err) throw err;
        res.render('PremierLeague',{data: results});
    });
 
      } else {
        res.redirect('../../login');
      }
    });

then in the .hbs file

  {{#each data}}
                <div class="card">
                    <h3>{{this.someData}}</h3>
                    <h2>{{this.someData}}</h2>
                </div>
            {{/each}}
anehme
  • 536
  • 1
  • 6
  • 18