Node.js: do not reinvent the wheel by serving static files from scratch

Node.js: do not reinvent the wheel by serving static files from scratch

In Node.js serving static files from scratch is quite a long and complex task.

In Node.js serving static files from scratch is quite a long and complex task.

Unlike traditional web servers, Node doesn't know in advance how to handle static files. You have to use the fs module to get the requested file from the filesystem, read its contents and output such contents in the correct way. For text files, such as HTML, CSS and JS files, the output will be a string while it will be a stream of bytes for binary files such as images.

Bear in mind that the fs module doesn't provide an utility for getting the content type of a file. The current alternative is to include a third-party module, provided that such module relies on a safer approach than simply getting the file extension for determining the MIME type to use.

Let's start with defining a generic handler for HTTP requests:

'use strict';

const http = require('http');
const fs = require('fs');

class RequestHandler {
    constructor(request, response) {
        this.req = request;
        this.res = response;
    }

    handle(path, method, cb) {

        let self = this;

        if(this.req.url === path && this.req.method === method) {
            cb(self.req, self.res);
        }

    }

}

We're going to use this handler both to serve HTML pages and a single image on a specified path:

const server = http.createServer((req, res) => {

    const handler = new RequestHandler(req, res);

    handler.handle('/', 'GET', (request, response) => {
        let home = fs.readFileSync('./templates/files.html').toString();
        response.writeHead(200, { 'Content-Type': 'text/html' });
        response.end(home);
    });

    handler.handle('/public/images/image.jpg', 'GET', (request, response) => {
        let srcPath = '.' + request.url;
        if(!fs.existsSync(srcPath)) {
            let notFound = fs.readFileSync('./templates/404.html').toString();
            response.writeHead(404, { 'Content-Type': 'text/html' });
            response.end(notFound);
        } else {
            let contents = fs.readFileSync(srcPath);
            response.writeHead(200, {
                'Content-Type' : 'image/jpeg',
                'Content-Length' : Buffer.byteLength(contents)
            });
            response.end(contents, 'binary');
        }
    });
});

server.listen(3000);

Note how HTML files are converted to strings before they are pushed to the output stream. Instead, our image has been served as a binary stream by using the correct parameter in the end() method. It should be clear now that handling manually a web server with Node is quite a complex and verbose task to accomplish. That's why we normally use an application framework such as ExpressJS.