NML Says

IT Infrastructure 1

Wladston Ferreira Filho, Computer Science Distilled: Learn The Art Of Solving Computational Problems, code.energy, 2017

Chapters 4 and 6.

Last Lessons’ Exercises

Using the Express Generator results in a project that has been made by the command npm init. It contains a manifest file

Example 1. package.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  "name": "project0",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www",
    "pretest": "npm i",
    "test": "nodemon ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "express": "^4.21.2",
    "http-errors": "~1.6.3",
    "morgan": "~1.9.1",
    "nodemon": "^3.1.9",
    "pug": "^3.0.3"
  }
}

As a present, to save a bit of time, I decided to give you:

Example 2. views/layout.pug
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
doctype html
html
    head
        title= title
        link(rel='stylesheet', href='/stylesheets/style.css')
    body
        header
            ul
                li
                    a(href='/') Home
                li
                    a(href='/users/register') Register
                li
                    a(href='/users/login') Login
                li
                    a(href='/quotes') Quotes
        main
            block content

and

Example 3. public/stylesheets/style.css
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
body {
  font: 16px "Lucida Grande", Helvetica, Arial, sans-serif;
  margin: 0;
  padding: 0;
}

a {
  color: #000066;
}

header {
    background-color: #999;
    margin: 0;
    padding: .5rem;
}
header ul {
    list-style-type: none;
    overflow: hidden;
    text-align: right;
}
header ul li {
    display: inline;
    padding-left: 2rem;
}
main {
    margin: 0;
    padding: 1rem 3rem;
}

Todays Stuff

Databases

To access a database from any application, a database service must be running on some host, and thus being available for queries.

In order to access such a service we must supply some information:

  • An URL
  • A port number
  • The name of the database
  • A userid
  • A corresponding password

The server must be configured such that the userid/password combo gives access to this particular database, in such a way that the CRUD activities to be executed from the application are allowed for that user.

A web application server is expected to receive many requests. Creating a connection to a database server from the application server for each request will be detrimental to performance. It would therefore be useful to do that once, when the application service is started, and then reuse this connection during the lifetime of the service.

For this we may create the following connection handler in the models folder:

Example 4. models/dbConnectionHandler.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* 
 * dbConnectionHandler.js
 * Copyright (c) 2025- Niels Müller Larsen
 * License MIT
 */

const mariadb = require('mariadb');
const pool = mariadb.createPool({
    host: process.env.DBHOST,
    database: process.env.DBNAME,
    user: process.env.DBUSER,
    password: process.env.DBPASSWORD
});

module.exports = class DbConnection {
    static dbh = false;

    static async connect() {
        if (!DbConnection.dbh) {
            try {
                DbConnection.dbh = await pool.getConnection();
            } catch(err) {
                throw err 
            }
        }
        return DbConnection.dbh;
    }
}

You will notice that the dbConnection class implements the singleton pattern, meaning that upon a connection request, if a connection exists it is returned, if not, on first request, it is created.

The values of the four named system variables are fetched from the .env file. They must be obtained from the administrator who is in charge of the database.

Since the existence of a working database connection is paramount to the application, we create it already at startup of the service by the following code in app.js:

Example 5. Fragment of /app.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ... from generator
const app = express();

// 
const models = require('./models/dbConnectionHandler');

const PORT = process.env.PORT;
app.locals.pretty = app.get('env') === 'development';

// connect to db
(async function() {
    try {
        const dbh = await models.connect();
        console.log('db connection, check!');
    } catch(err) {
        console.log(err.message);
        process.exit();
    }
})();

// ...
// from generator

Middleware

The controller level of an MVC pattern is often implemented as a router and a controller. On top of that sometimes the router is separated into several routers based on some semantics. Thus the generator has two routers routes/users.js for handling issues around the users, and routes/index.js for the rest, whatever the application is about.

Example 6. The General Router routes/index.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const express = require('express');
const con = require('./controllers/controllers.js');

const router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

/* POST quote */
router.post('/quote', con.isAuth, con.postQuote, con.fetchQuotes, function(req, res, next) {
    res.render('/quotes', {
        title: 'Quotes',
        rows: res.locals.quotes
    });
});



module.exports = router;

In example 6, lines 11 through 17, a route for inserting quotes into the database is exemplified. In line 12, con.isAuth, con.postQuote, con.fetchQuotes, are middleware we shall make in order to enter quotes into the database, and then show all quotes in the next screen. The prerequisite is in line 2, giving us an address of the controller js-file holding the three middleware functions.

The arguments req, res, next are per custom sent to the middleware functions. req is the request object, giving us whatever is sent from the requestor, eg the form data. res is the response object we are building to be returned to the user when we are done. next is an express function that will call the next middleware function when invoked. This means that whenever we have achieved what we wanted in some middleware function, we MUST call next() to exit and pass control to the next middleware.

Example 7. The Users Router routes/users.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var express = require('express');
var router = express.Router();

/* GET user registration page */
router.get('/register', function(req, res, next) {
  res.render('register', {
      title: 'Registrer dig:'
  });
});

/* POST user registration */
router.post('/register', function(req, res, next) {
    res.send(`received ${req.body.email}`);
	// todo:
	// create user table in database
	// create middleware to handle form data
	// ie hash password and call model
	// and model to be called from middleware handler
});

// more to be done

/* GET login page */
// modelled after registration page above


/* POST login */
// create controller function to hash password
// and model function to read user
// when read, controller must verify password and userid
// and if ok, issue some kind of 'logged in' token.

module.exports = router;

Exercises

Exercise ITI.1.0

The exercise today is completing what we have started above.

One obvious omission above is setting up the database server if we do not already have one.

When that is done, the user table, and the quotes tables must be created.