express-accelerate

An express interface module for caching and formatting responses.

Usage no npm install needed!

<script type="module">
  import expressAccelerate from 'https://cdn.skypack.dev/express-accelerate';
</script>

README

Logo

Accelerate

Accelerate is a NodeJS module made to interface with ExpressJS to offer a library of utilities to help develop web applications faster and more intuitively. View the API reference to sift through each of the features that exist in the library.

Application Logging

Accelerate's logging offers a rich, but light-weight set of features that will help power your express application. The idea is to create multiple instances of a logger for various parts of your application. For example, an SQL logger for SQL errors, warnings, and information, a Web logger for logging ExpressJS messages, and a cache logger for logging cache related details. Each logger can be configured with a varied set of rules and write intervals to help manage how the logger behaves.

For starters, you can create a logger like so:

const accelerate = require("express-accelerate");
const MainLogger = new accelerate.LoggerInstance({
    root: __dirname,
    file: "./logs/report.log",
    header: "MAIN"
});

Each logging instance needs a root path given to it as well as the directory and file name of where the log contents are stored. The header is what appears in the front of the log message dictating where/what the log is. In this case, the header is MAIN.

You can write to the logger easily enough:

MainLogger.write("info", "This is an information level message.");
// Prints: [MAIN][INFO][09/17/2018 12:45:32PM] : This is an information level message.

There are 5 logging levels that can be used in order of severity: error, warn, info, debug, verbose.

They all have a defined color which can be modified by going to the module folder: node_modules/express-accelerate/lib/logger.js and changing the globalColors values to fit your needs. This is a temporary solution to changing the colors of each log level since colors globally modifies the string instance. In future releases you will see that globalColors will exist in the config and are not globally attached to the string object.

LoggerInstance Configuration

Below is the default configuration JSON object for the LoggerInstance class.

{
    file: "report.log",         // File location + name.
    root: null,                 // Root directory to be used.
    header: "MAIN",             // Header to appear in front of logs.
    bufferInterval: 1000,       // Duration between each file write.,
    rules: {                    // Dictates if certain levels get added to the file.
        verbose: false,
        debug: false,
        info: true,
        warn: true,
        error: true
    }
}

If you choose to modify the rules propert in the configuration, ensure you define all of the rules in the child object. In future releases this will be changed. This is due to the implementation only checking the top layer of keys and does not check in child-layers.

LoggerInstance.write() Optional Configuration

Below is the default configuration JSON object for the write method in a LoggerInstance class.

{
    append: true,           // Determines if this particular log gets added to the log file.
    forceBuffer: false,     // Consumes the buffer immediately after writing.
    silent: false           // Does not show in app-console.
}

Application Caching

Accelerate offers in-application memory caching. This is perfect for low-medium scale web application projects that don't consume much memory over long term use. So long as your application does not dynamically add to the cache, then this will work perfect for you. For more data intensive applications, it is best to use your own caching module or service or know the bounds that your application can exist in.

Accelerate's application cache uses timers to determine when data should set to be garbage collected. Any time a user implements a new cache object, it will be given a default lifetime of 2 minutes or a user-supplied lifetime (a UNIX timestamp of a future time, recommending the use of MomentJS as a means of creating this timestamp).

The cache is checked in a set interval, by default is every 1 second (1000ms) which can be modified by passing in a configuration to the CacheInterface constructor (see below). This can be lowered rather reliably without casting too much overhead on performance. The use of javascript intervals allow the application to sit idle between each call. The cache is only checked when objects are present in the cache.

Creating a cache interface is relatively simple:

const accelerate = require("express-accelerate");
const CacheInterface = new accelerate.CacheInterface({
    checkInterval: 200 // Checks the cache every 200ms.
});

This will create an interface that allows you to add to the cache. The cache is stored as a global object and thus creating multiple interfaces access the same location in memory. Adding to the cache is super simple as well:

const moment = require("moment");

// Using MomentJS
CacheInterface.set("mykey", {my: "data"}, {
    lifetime: moment().add(30, "seconds").unix() // Lasts for 30 seconds
});

// Creating a timestamp using javascript date
CacheInterface.set("another", {foo: "bar"}, {
    lifetime: Math.round((new Date()).getTime() / 1000) + 30 // Lasts for 30 seconds
});

Getting from the cache is by far the easiest part of this entire process. This will reliably return null or data depending on whether the key exists or if the data exists/expired.

let data = CacheInterface.get("mykey"); // Returns contents of mykey if it isn't expired.
let data2 = CacheInterface.get("nothing"); // Wasn't added to the cache, most likely returns null.

Data Retrieval with Routines

At it's fundamental use, routines are superflous, but when used correctly can offer a level of abstraction that can most certainly improve the layout of your Express application routes. Typically speaking, routines are simply functions that retrieve data that are stored as key/value pairs. You can invoke a routine by providing a key and the routine handler class will fetch it for you.

The real power lies in the abstraction mechanisms of providing keys--if you had a route with variable parameters such as an API interface your path may look like this: /data/sales/:when. By defining all the available/possible route paths such as /data/sales/day or /data/sales/week as the keys to the routine, then you can pass in that to the retrieve function of the routine handler and it will fetch & run the routine to get the data. In the event that it does not find the route (because the user requested a bad route or it was not defined), you can easily send a 404 or 500 response. This takes out the tedious nature of writing if statements and completely gunking up your Express routing with huge logic.

You can start by defining a routine like this:

const accelerate = require("express-accelerate");
const sql = require("some-sql-database-module");
const RoutineHandler = new accelerate.RoutineHandler();
const CacheHandler = new accelerate.CacheHandler();

// Callback must be added as the function parameter.
// You must fill each endpoint by invoking the callback properly.
RoutineHandler.create("/data/sales/day", (callback) => {

    // Retrieve from the cache first.
    let data = CacheHandler.get("/data/sales/day");

    // If the cache found data send the data back, if not get the data and send it back.
    if (data) callback(null, data); // callback(error, data) standard
    else {

        sql.query("SELECT * FROM SALES_DATA", (error, rows) => {

            if (error) callback(error, null);
            else {

                callback(null, rows); // Send the data back through the callback.
                CacheHandler.set("/data/sales/day"); // Set the cache data.

            }

        });

    }

});

// Condensed.
RoutineHandler.create("/data/sales/week", (callback) => {
    let data = CacheHandler.get("/data/sales/week");
    if (data) callback(null, data);
    else {
        sql.query("SELECT * FROM SALES_DATA", (error, rows) => {
            if (error) callback(error, null);
            else {
                callback(null, rows);
                CacheHandler.set("/data/sales/week");
            }
        });
    }
});


Which in affect can be invoked from an Express route like so:


app.get("/data/sales/:time", (request, response) => {

    let time = request.params.time;
    RoutineHandler.retrieve("/data/sales/"+time, (error, data) => {

        // If an error, send a 404 and then log the error.
        if (error) {
            response.send(404);
            console.log(error);
        } else {
            response.send(data); // Send the data to the user.
        }

    });

});

As you can see, you wouldn't necessarily attempt to invoke a routine strictly by it's name, you would want to use a variable path name as a means of retrieving from a group of routines that are defined. If the client decides to tamper with the front-end request code, the routine handler will send an error through the callback thus allowing you to send a 404 response.