Server Reads Requested Assets, Dynamic Routes, HTML files

We have just seen statically routed serving of an asset read by the servers fs. Let change it a bit to cater for dynamic routing. Dynamic in the sense that whatever url you address will be looked for by the router. I found it will be served, if not we get a 404.

Again, following the best practice outlined in the section called “Project Creation - Best Practice” we create a project myg61 remotely, clone it, prepare the package.json, and before we get to the server, we create a project subdirectory views, and in that we create a boilerplate index.html for welcoming users to our site. The static front page if you will. Here you may use a copy of the one we just saw in the previous section. The dynamic routing means that you may add .html files to the views directory, and they will be routed to and served if addressed by the url.

Indeed the main.js, and the server.js need not be changed. They are thus far sufficiently dynamic. They are repeated below for ease of reading. The router and the handlers are naturally project specific, as stated before, and must be adapted to the current project.

Example 17.6. Routed and Modularized Application, myg61/main.js, Unchanged!
"use strict";

var server = require("./bin/server");               // make server module available
var router = require("./routes/router");            // router module

server.start(router);                               // start server
                                                    // callback to route

Example 17.7. The Server as a Module, myg61/server.js, Unchanged!
"use strict";
/*
 *  new server.js adds request body data
 */
const http = require("http");                   // http module
const lib = require("../private/libWebUtil");   // home grown utilities
const hostname = "localhost";
const port = Number(process.argv[2]) || 3000;

module.exports = {
    start(router) {
        const server = http.createServer();

        server.on("request", function (req, res) {      // eventhandler for "request"
            console.log(lib.makeLogEntry(req));         // home made utility for logging
            let body = [];
            req.on("data", function (bodyData) {        // eventhandling for data reception
                body.push(bodyData);                    // bodyData is an object
            });
            req.on("end", function () {                 // eventhandling for end-of-data
                body = Buffer.concat(body).toString();  // body2string
                router.route(req, res, body);           // pass to router
            });
        });

        server.listen(port, hostname, function () {
            console.log(`Log: Server started on http://${hostname}:${port}/`);
        });
    }
}

Example 17.8. The Router as a Module, myg61/router.js

The router has been changed here to accomodate the /home route as well as the /side which we shall return to promptly.

"use strict";
/*
 * check if routed handler function exists
 * if yes call it, else complain
 */
const handlers = require("./handlers");               // handlers module

const requestHandlers = {                             // application urls here
    "/home": handlers.home,
    "/side": handlers.side,
    "/notfound": handlers.notfound,
}

module.exports = {
    route(req, res, body) {
        if (typeof requestHandlers[req.url] === 'function') { // look for route
            requestHandlers[req.url](req, res);               // if found use it
        } else {
            requestHandlers["/notfound"](req, res);           // use notfound
        }
    }
}

Example 17.9. The Handlers in a Module, myg61/handlers.js
'use strict';
/*
 * handlers.js
 * Requesthandlers to be called by the router mechanism
 */
const fs = require("fs");                   // file system access
module.exports = {
    home(req, res) {
        fs.readFile("views/index.html", function(err, data) {
            if (err) {
                res.end("<h1>The page you wanted doesn't exist</h1>");
            }
            res.write(data);
            res.end();
        });
    },
    side(req, res) {
        fs.readFile("views/side.html", function(err, data) {
            if (err) {
                res.end("<h1>The page you wanted doesn't exist</h1>");
            }
            res.write(data);
            res.end();
        });
    },
    notfound(req, res) {
        console.log(`Handler 'notfound' was called for file ${req.url}.`);
        res.end();
    }
}

On your CLI do npm test to start the server. Then go to your browser and test the following url(s).

Check the browser screen as well as the console log in each case.

We have prepared an extra page for you:

Example 17.10. An HTML5 Page with Assets, views/side.html
<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>myg6n Side Demo Page</title>
        <link rel="stylesheet" href="side.css"/>
        <script src="side.js"></script>
    </head>
    <body>
        <header>
            <h1>Welcome World!</h1>
        </header>
        <main>
            <p>
                To our regular old fashioned frontend static homepage.
            </p>
            <p style="background-color: black;">
                <img src="iau_rocks.png" alt="ia rocks"/>
            </p>
        </main>
        <footer>
            <p>
                &copy; nml
            </p>
        </footer>
    </body>
</html>

Now we show this: file:///home/nml/nodeMyGPNode/myg61/views/side.html in our browser in order to verify that the css, js, and image works. Then addressing the page through http://localhost:3000/side we see that it doesn't. Let us talk a bit about the HTTP protocol to understand why not. Then we shall move on to the next section to make it work.