Posts › Common HTTP Tasks in Bare NodeJS

2019-04-02

There are a plethora of NodeJS app frameworks out there that make writing server-side applications easier at the cost of an ever-inflating node_modules folder and performance.

As serverless / lambda services become more prevalent, the impact of framework decisions can be felt on performance. NodeJS has been around since 2009 and its API is increasingly comprehensive. Do you really need a framework like Express, Next or Fastify?

If, for instance, your app server is part of a set of internal tools (e.g. mock API server or local build server) the plugin ecosystems and features of these frameworks matter much less. This post details some common HTTP tasks: these aren't full-featured replacements. Instead, they are meant to illustrate the fact that the NodeJS HTTP API is relatively simple and that it is easy to create alternatives to common HTTP frameworks.

Set up

This simple scaffold code is all you need to start using the following examples. You can paste each example code block into lines where ...X appear to implement the corresponding functionality.

const http = require('http');
const PORT = process.env.PORT || 3000;

...A

http.createServer((req, res) => {
    
    ...B

}).listen(PORT);

1. Serving files synchronously from a directory

This code can be used as a zero dependency alternative to express.static().

// A
const {readFileSync, existsSync} = require('fs');

function serveFile(basePath, req, res) {
    const pathname = req.url.split('?')[0];
    const filePath = basePath + pathname;

    if (pathname === '/') {
        res.end(readFileSync(filePath + 'index.html'));
    } else if (existsSync(filePath)) {
        res.end(readFileSync(filePath));
    } else {
        res.statusCode = 404;
        res.end();
    }
}

// B
serveFile('.', req, res);

Limitations:

2. Retrieve a JSON object from POST requests

As a lightweight option vs body-parser, this example returns a promise that resolves when body is parsed or error is encountered.

// A
function respondToPOST(req, res) {
    return new Promise((resolve, reject) => {
        let body = '';

        req.on('data', chunk => body += chunk.toString());
        req.on('error', () => reject());
        req.on('end', () => {
            const json = {};

            body.split('&').forEach(line => {
                const keyvalue    = line.split('=');
                json[keyvalue[0]] = decodeURIComponent(keyvalue[1]);
            });

            resolve(json);
        });
    });
}

// B
respondToPOST(req, res);

Limitations:

3. Routing requests based on URL path

Stand ins for express.get() and express.post().

// A

function getRoute(path, cb) {
    return function(req, res) {
        if(req.method === 'GET' && req.url.indexOf(path) === 0) {
            cb(req, res);

            return true;
        } else {
            return false;
        }
    };
}

function postRoute(path, cb) {
    return function(req, res) {
        if(req.method === 'POST' && req.url.indexOf(path) === 0) {
            cb(req, res);

            return true;
        } else {
            return false;
        }
    };
}

const ROUTES = [
    getRoute('/get-route-matcher',   (req, res) => res.end('GET route was hit')),
    getRoute('/get2-route-matcher',  (req, res) => res.end('second GET route was hit')),
    postRoute('/postal',             (req, res) => res.end('POST route was hit'))
];


// B
const hasMatchedRoute = ROUTES.some(f => f(req, res));

if(!hasMatchedRoute) res.end('not found');

Limitations

Imagine your NodeJS apps running faster

Cohesive.dev can help you identify and cut bloat out of NodeJS codebases to bring about better development and customer experiences. Contact us for an initial consultation to give your apps and services the speed boost they deserve.