smidig

A simple, small and flexible API

Usage no npm install needed!

<script type="module">
  import smidig from 'https://cdn.skypack.dev/smidig';
</script>

README

smidig

smidig is a simple, small and flexible framework that provides a container to build APIs for your application. It allows you to store both values and functions identified with a unique path.

Installation

smidig can be installed using npm:

npm install smidig

Using require() the package can be imported. The returned function has to be invoked to create a new API storage. This allows you to use multiple separated instances of it.

var smidig = require("smidig");

var myAPI = smidig();

myAPI.set( .... ); // Set a value; see below
myAPI.get( .... ); // Get a value; see below

Storing values

The smidig.set function is used to store both values and functions.

smidig.set( where, what, whereContext )

It expects three arguments:

  1. where is an absolute path like /foo/bar. where can also be a function which is executed and whose return value will be used as a path if it is neither undefined nor null nor a function.
  2. what is the value to set.
  3. Optional: whereContext is an Array with arguments passed to the function where.

Although you can provide an array of arguments, the first argument to where will always be what. Here is an example that demonstrates the setter function:

var smidig = require("smidig")();

function genPath(value,prefix) {
    return "/"+prefix+"/"+value;
}

smidig.set(genPath, 'foobar', ['_pref'] );
smidig.set("/hello/world","hello, world");

The function genPath is used to generate the path for the API. It arguments are the value to set ("foobar") and a prefix ("_pref"). It will generate the path "_pref/foobar" which will contain the value "foobar".

Deleting values

The smidig.unset function can be used to remove values from the API storage.

smidig.unset( where )

As with smidig.set it is possible to provide a function that will be executed and whose return value will be used as a path.

smidig.unset('/hello/world');

var func = function() { return '/delete/me'; };

smidig.unset( func );

Retrieving values

smidig.get is used to get values from the API. At this point it becomes relevant if you have stored a function or a value. Functions can be executed and their return value will be returned.

smidig.get( where, context, executeFunctions )

The three arguments:

  1. where can be a path or a function that will be executed and whose return value will be used as the path.

  2. A context given as this to the function stored in the API (if any)

  3. A optional boolean indicating if stored functions should be executed. Default value is true.

    smidig.get('/foo/bar'); smidig.get('/a/function', ['arg1','arg2']);

Direct access

Although the smidig.get function allows you to access stored values, the hierarchical structure of the API let you access your paths directly without calling a function:

var smidig = require("smidig");
var myAPI = smidig();

myAPI.set('/math/calc/exp',Math.exp);

myAPI.api.math.calc.exp(4);         // 54.598150033144236

Using the .api property you can access all the stored properties without the overhead of a function.

This way, you have both the ability to use a direct ("static") access to your API and the ability to access it with a string that might be given using another framework like express.

The .api property is a read-only attribute that cannot be changed.

var storage = smidig();
console.log( storage.api );     // { }
storage.api = "changed?";
console.log( storage.api );     // { } - no change here

Longer example

Here is an example demonstrating how smidig can be used:

var smidig = require("smidig");

var firstAPI = smidig();
var secondAPI = smidig();


function generatePrivatePath( value, name, keys ) {
    var path = "/_private/";

    if( 'string' === typeof keys && '' !== keys.trim() )
        path += keys + "/";

    return path + name;
}

function storeFunction( fn ) {
    return '/fn/'+fn.name;
}

// Store some functions
firstAPI.set(storeFunction, Math.max);    // stored as /fn/max
secondAPI.set(storeFunction, Math.min);

// Some 'private' keys
firstAPI.set( generatePrivatePath, 'password123', ['password','users/johndoe']);

// Let's get all the things we have stored
console.log("Password for johndoe:", firstAPI.get(generatePrivatePath,[],['password','users/johndoe']) );  // password123
console.log("/fn/max for 4,5 is ", firstAPI.get('/fn/max', [4,5] ));                                       // 5
console.log("/fn/min for 99,41,-4 is ", secondAPI.get('/fn/min',[99,41,-4]));                              // -4
console.log("/fn/sqrt for 1024 is ", firstAPI.get('/fn/sqrt', [1024] ));                                   // undefined


// More flexibility
console.log("Good usage example result:", secondAPI.api.fn.min(99,41,-4) );         // -4

Building RESTful APIs with express

smidig comes with an express middleware function that allows you to build RESTful APIs with ease. The express integration is not limited to HTTP access but set up and accessed as described above. Thus, you can allow clients to use the same API you are working with on your server.

Registering the handler

smidig provides a function that returns an express middleware function. A middleware function is invoked before a function registered with express.VERB (e.g. express.post(...)).

smidig.express( apiPrefix, apiObject, functionContext )

The smidig API middleware function will handle every request whose request path begins with the given prefix (first argument: apiPrefix). Other requests are ignored and have to be handled somewhere else.

Pass your smidig API object as the second parameter.

An optional function context can be given as a third argument. This context will be passed to functions stored in the API as this. The default context contains the api and the api path prefix as well as request related information (see below).

defaultContext = {
    api: API_object,
    target: path_prefix
};

Function contexts

Both the default function context as well as a user defined context are extended w/ request information such as the path (relative to target) and query information. These properties will be overriden without checking for existence, keep that in mind when using this feature.

injectedProperties = {
    path:   relative_request_path,
    query:  GET_parameters,
    body:   POST_body
};

Thus, the minimal this context passed to functions stored in the API looks like this:

this = {
    api:     API_object,    // Only in default context
    baseurl: path_prexix,   // Only in default context
    
    path:    relative_path, // always
    query:   GET_params,    // always
    body:    POST_params,   // always
};

Return values

Functions are be executed and they return value is serialized. To provide valid JSON responses the API has default JSON for strings, numbers, booleans and functions as well as undefined and null. If a function returns an object or an object is stored in the API, it is serialized with JSON.stringify and send to the client. Thus, you have to make sure that your object can be converted into a string.

/* String */
{ type: 'string-result', result: your_string }

/* Boolean */
{ type: 'boolean-result', result: your_boolean }

/* Number */
{ type: 'number-result', result: your_number }

/* Function */
{ type: 'function-result', result: your_fn_as_string }

/* undefined */
{ type: 'smidig-result', result: 'undefined' }

/* null */
{ type: 'smidig-result', result: 'null' }

Example

Here is a working example for a simple API:

var smidig = require("smidig");
var express = require("express");

var app = express();
var api = smidig();

api.set('/fn/max', function(){
    // Will always return 4
    return Math.max(3,4);
});

api.set('/hello', function(){
    return {
        message: 'hello from ' + this.path
    };
});

// Return number 42
api.set('/number/42', 42);

/*
 * Accessing this function will return
 *
 *  {"fn":{},"number":{"42":42}}
 *
 * in the backend and
 *
 *  {"target":"/api/","api":{},"path":"/context","query":{}}
 *
 * when using an HTTP client
*/
api.set('/context', function(){

    // REST?
    if( this.path )
        return this;
    // Backend access
    else
        return JSON.stringify(this);

});


console.log( api.api.context() );   // {"fn":{},"number":{"42":42}}

// Setup the handler with default context
app.use( smidig.express('/api/', api ) );

// Handle all other requests
app.all('*', function(req,res){
    res.end('Handler did not handle the request');
});

app.listen(1337);

The API can be accessed with a web browser at http://localhost:1337/api/api_path. Requesting http://localhost:1337/api/context will print the this context. A good point to start with smidig's context mechanism.