Server Reads Requested Assets, Dynamic Routes

Now we are going to make our server realistic in the sense that routes are assigned handlers that will perform the actual activities associated with each route. First we create a separate router.js file that will hold the code implementing the principle. The handlers are changed accordingly, and modified to adapt to the DRY principle. Don't Repeat Yourself.

Example 18.1. The Application. myg63/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 18.2. The Server. myg63/bin/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 18.3. Dynamic Routes Router, myg63/routes/router.js
"use strict";

const handlers = require("../private/handlers");    // handlers module
const httpStatus = require("http-status-codes");
const contentTypes = {
    "text": { "Content-Type": "text/plain; charset=utf-8" },
    "start": { "Content-Type": "text/html; charset=utf-8" },
    "js": { "Content-Type": "application/js" },
    "css": { "Content-Type": "text/css" },
    "png": { "Content-Type": "image/png" },
    "jpg": { "Content-Type": "image/jpg" },
    "gif": { "Content-Type": "image/gif" },
    "ico": { "Content-Type": "image/x-icon" },
    "svg": { "Content-Type": "image/svg+xml" }
};

const routes = {                                    // register handles to routes
    "GET": {
        "/start": handlers.getAndRespond,
        "/side": handlers.getAndRespond,
        "/about": handlers.getAndRespond,
        "/contact": handlers.getAndRespond,
        "js": handlers.getAndRespond,
        "css": handlers.getAndRespond,
        "png": handlers.getAndRespond,
        "jpg": handlers.getAndRespond,
        "gif": handlers.getAndRespond,
        "ico": handlers.getAndRespond,
        "svg": handlers.getAndRespond
    },

    "POST": {
        "/contact": handlers.receiveData
    }
};

exports.route = function(req, res, body) {          // routing
    let asset;
    let type;
    let routedUrl;
    if (req.url.indexOf(".js") !== -1) {            // check for asset types
        asset = "js";
        routedUrl = "public/javascripts" + req.url;
        type = contentTypes.js;
    } else if (req.url.indexOf(".css") !== -1) {
        asset = "css";
        routedUrl = "public/stylesheets" + req.url;
        type = contentTypes.css;
    } else if (req.url.indexOf(".png") !== -1) {
        asset = "png";
        routedUrl = "public/images" + req.url;
        type = contentTypes.png;
    } else if (req.url.indexOf(".jpg") !== -1) {
        asset = "jpg";
        routedUrl = "public/images" + req.url;
        type = contentTypes.jpg;
    } else if (req.url.indexOf(".gif") !== -1) {
        asset = "gif";
        routedUrl = "public/images" + req.url;
        type = contentTypes.gif;
    } else if (req.url.indexOf(".svg") !== -1) {
        asset = "svg";
        routedUrl = "public/images" + req.url;
        type = contentTypes.svg;
    } else if (req.url.indexOf(".ico") !== -1) {
        asset = "ico";
        routedUrl = req.url;
        type = contentTypes.ico;
    } else {
        if (req.url.charAt(req.url.length - 1) === "/") {
            asset = "/start";
            routedUrl = "views/index.html";
            type = contentTypes.html;
        } else if (req.url === "/start") {
            asset = req.url;
            routedUrl = "views/index.html";
            type = contentTypes.html;
        } else {
            asset = req.url;
            routedUrl = "views" + req.url + ".html";
            type = contentTypes.html;
        }
    }

    try {
        if (routes[req.method][asset]) {            // does handler exist to this route
            console.log("routing: " + asset + " file: " + routedUrl);
            routes[req.method][asset](routedUrl, type, res);  // yes, call it with params
        } else {                                // no, return error msg
            console.log("nmlnf: " + req.url);
            res.writeHead(httpStatus.NOT_FOUND, contentTypes.text);
            res.end(`route for <kbd>${req.url}</kbd> not found`);
        }
    } catch (ex) {                              // routing exception
        console.log("Log: Routing exception: " + ex);
    }
};

Example 18.4. The Handlers, myg63/private/handlers.js
'use strict';
/*
 * handlers.js
 * Requesthandlers to be called by the routing mechanism
 */
const fs = require("fs");                           // file system access
const httpStatus = require("http-status-codes");

const goError = function(res) {
    res.writeHead(httpStatus.NOT_FOUND, {   // http page not found, 404
        "Content-Type": "text/html; charset=utf-8"
    });
    res.write("<h1>404 Not Found</h1>");
    res.end();
};

exports.getAndRespond = function(path, contentType, res) {
    console.log(path);
    if (fs.existsSync(path)) {              // does file exist, sync
        fs.readFile(path, function(err, data) { // read
            if (err) {                      // if read error
                console.log("nml: " + err);           // inform server
                goError(res);               // inform user
                return;                     // back to caller
            }
            res.writeHead(httpStatus.OK, contentType); // prep header
            res.write(data);                // prep body with read data
            res.end();                      // send response
        });
    } else {
        goError(res);                       // doesnt exist error
    }
};

On your CLI do npm test to start the server. Then go to your browser and test each of the following urls.

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