Simple Http Server

Node.js makes it remarkably simple to develop an HTTP server quickly without having to write low level socket code. In-fact all it takes is a few lines of code. In this article I'll explain how to develop a simple HTTP server and then expand on it to handle routing and serving static files.

Node.js provides an http module, which can we used to create either client or server applications. Listing 1 below outlines the minimum code required to create an HTTP server.

Listing 1

const http = require('http');
    
http.createServer((req, res) => {
    res.end("Hello there!")
}).listen(5050);

When a request is made to the server, the callback function supplied to the createServer method is executed. This callback method provides the request and response objects, which are used to receive and send data. It is import to always end the response using the end() method. Failure to do so, will result in the server hanging. The listen() method is used to specify the port number the server should listen on.

Handling Routes


The http module is a low level module, which allows you to create a server but does not provide any methods to handle routes. You will need to implement your own route handling logic, such as mapping a request to a function. Thankfully the request object contains all the information we need to implement our own route handlers. Listing 2 below shows a simple example of how to execute a statement based on the request URL.

Listing 2

const http = require('http');
    
http.createServer((req, res) => {
    let url = req.url.toLowerCase();

    if(url == "/" || url == "/index.html"){
        res.end("Index page");
    }
    else if(url == "/aboutus.html"){
        res.end("About us");
    }
    else if(url == "/contact.html"){
        res.end("Contact us");
    }else{
        res.end("404 Page not found");
    }
    
}).listen(5050);

The code above can now execute a code block based on the request URL and even show a 404 Page not found message when a route does not exist. The routes in the sample code above are all dynamic, that is the file index.html does not actually exist. Further more, index.html might contain an image or include css/javascript files, which maybe physical files. The HTTP server needs to be able to respond to requests for files that exist. Listing 3 below shows how to response to static files such as images using the fs module.

Listing 3

const http = require('http');
const fs = require('fs');
    
http.createServer((req, res) => {

    let name = (req.url == '/') ? 'index.html' : req.url;

    let file = __dirname + '/' + name;

    fs.readFile(file, (err, data) => {

        if(err){
            let url = req.url.toLowerCase();

            if(url == "/" || url == "/index.html"){
                res.end("Index page");
            }
            else if(url == "/aboutus.html"){
                res.end("About us");
            }
            else if(url == "/contact.html"){
                res.end("Contact us");
            }else{
                res.end("404 Page not found");
            }
        }else{
            res.end(data);
        }
    });

}).listen(5050);

The code above will first check if a file with the requested URL exists using the fs module. If the file exists, it's contents is sent to the browser. If the file does not exist, then we use dynamic routes. Executing dynamic routes using if statements makes the code prone to errors and the readability of the code is greatly affected as more routes are used. Let's refactor the code so that each route executes a callback function. To do this, we will enclose the server in a new class called HttpServer as shown in listing 4.

Listing 4

const http = require('http');
const fs = require('fs');
    
class HttpServer{

    constructor(){
        this._routes = {};
        this._error = null;
    }

    route(url, callback){
        this._routes[url] = callback;
        return this;
    }

    error(callback){
        this._error = callback;
        return this;
    }

    listen(port){
        http.createServer((req, res) => {

            let name = (req.url == '/') ? 'index.html' : req.url;
        
            let file = __dirname + '/' + name;
        
            fs.readFile(file, (err, data) => {
        
                if(err){
                    let url = req.url.toLowerCase();
        
                    if(this._routes.hasOwnProperty(url)){
                        this._routes[url](req, res);
                    }else{
                        this._error(new Error('404 Page Not Found'), req, res);
                    }
                }else{
                    res.end(data);
                }
            });
        
        }).listen(port);
    }
}

var app = new HttpServer();

app.route('/aboutus.html', (req, res) => {
    res.end('About us page');
}).route('/contactus.html', (req, res) => {
    res.end('Contact us page');
}).error((err, req, res) => {
    res.end(err.message);
}).listen(5050);

We now have a reusable class that encapsulates our HTTP server and provides us with easy to use methods. The route() method accepts two arguments, a URL and a callback function and returns the current instance, which allows us to do method chaining. When a route is registered, it is placed in a routes collection. The error() method allows us to specify a callback function to handle 404 Page Not Found.

Summary


In this article I've explained how to create a simple HTTP server that can be used to serve static and dynamic content. I've purposefully left out some code such as sending response headers to keep the code as simple as possible. In the next article, I'll expand on the HttpServer class by adding support for sessions.

HANA DB

Rust

Java

SAP Business One

Node.js