tasklist - Client Application for a NodeJS REST API- Part I

A few days ago, I posted a NodeJS application built around a REST API, and some further thoughts around architecture with the NodeJS mysql package. I have pushed the complete tasklist application to GitHub. If you want to grab it and see it run, clone the repo:

$ git clone https://github.com/robertdmunn/tasklist

Today, I am going to start a short series of posts going into some detail about the client application and how it interacts with the REST API.

Warning

Before I get started, let me be clear about what I am showing. The client application is an example implementation of a JavaScript framework I wrote last year (now almost two years ago) as an academic exercise, general exploration of some features of JavaScript, and bringing together of some things I had written in the past. That framework, called altseven, is now available on GitHub:

$ git clone https://github.com/robertdmunn/altseven

and can be installed through npm:

$ npm install altseven

altseven relies on several external packages, including an experimental UI+ library called gadget-ui I started a few years ago and have worked on sporadically ever since. Also available separately on GitHub and npm, if you are interested.

There might be an element or two of gadget-ui being run in production, but mostly it's just a playground for ideas that I have toyed with. You can see my posts about the gadgetui.input.FileUploader component, which is one of the things that might actually be in production use. 

Enough with the warnings, let's look at some code.

Serving Static Content with NodeJS

In my previous posts about the REST API in NodeJS,  I left out a piece of code in the index.js that serves static content for the client application, since it wasn't relevant to the topic. Here is the index file with the missing code:

index.js
----------


const express = require( 'express' );

const app = express();

// map our client-side libraries
app.use( express.static( 'client' ) );
app.use( "/lib/gadget-ui", express.static( 'node_modules/gadget-ui' ) );
app.use( "/lib/altseven", express.static( 'node_modules/altseven' ) );
app.use( "/lib/modlazy", express.static( 'node_modules/modlazy' ) );
app.use( "/lib/velocity", express.static( 'bower_components/velocity' ) );
app.use( "/lib/mustache.js", express.static( 'bower_components/mustache.js' ) );
app.use( "/lib/open-iconic", express.static( 'node_modules/open-iconic' ) );

// routes for the API
require( './routes/tasks.js' )(app);

// set our listener
var server = app.listen( 4000, function(){

});

Let's examine what's going on here.

First, you can see we're using Express in the Node application. Fortunately, Express includes the app.use() and express.static() functions to make it easy. To serve static content to the root of the application, we see the first line:

app.use( express.static( 'client' ) );

By passing the express.static( 'client' ) argument without a corresponding path to app.use(), we set the 'client' folder as the root of the static content that will be served by NodeJS.

Following the root folder, we specify both a path from the root and a static folder to serve on that path:

app.use( "/lib/gadget-ui", express.static( 'node_modules/gadget-ui' ) );

But why not use Browserify or Webpack? That's certainly something that can be done, and I'll cover it in a future post. For now, app.use() with express.static() is an easy way to serve static assets from NodeJS without a Web server.

Now let's look at the "old school" index.html, complete with dependency loader.

index.html
--------------

<html>
  <head>
    <title>
      NodeJS Task List
    </title>
    <script src="/lib/modlazy/dist/modlazy.min.js"></script>
    <script>
      modlazy.load(["/js/app.components.js < /js/app.remote.js < /js/app.utils.js < /js/app.main.js < /js/app.events.js < /lib/altseven/dist/a7.js < /lib/gadget-ui/dist/gadget-ui.js < /lib/velocity/velocity.js ",
        "/lib/mustache.js/mustache.js", "/css/styles.css", "/lib/altseven/dist/a7.css",
        "/lib/gadget-ui/dist/gadget-ui.css",
        "/lib/open-iconic/font/css/open-iconic.css"
      ], function() {
        var
          options = {
            auth: { // sessionTimeout: ( 60 * 15 * 1000 ) // default time in  milliseconds to refresh system auth
            },
            console: {
              enabled: true,
              wsServer: 'ws://127.0.0.1:8000',
              top: 100,
              left: 800,
              height: 300,
              width: 500
            },
            logging: {
              logLevel: "INFO,ERROR,FATAL,TRACE"
            },
            remote: {
              modules: app.remote,
              useTokens: false // defaults to true for the auth system
            },
            ui: { // renderer: // renderer is implicitly set by existence of the templating library, currently Mustache or Handlebars
              templates: "/templates.html"
            }
          };
        var p = new Promise(function(resolve,
          reject) {
          a7.init(options, resolve, reject);
        });
        p.then(function(state) {
          app.main.init(state);
        });
        p['catch'](function(message) {
          console.log(
            "Something went wrong.");
        });
      });
    </script>
  </head>

  <body>
    <div name="main">
  		<div name="app" >

  		</div>

  	</div>

  </body>
</html>

We don't need to spend too much time on the loading of the assets. modlazy is a dependency loader that guarantees loading of your JS dependencies in the correct order. The arrows "<" indicate dependency, so assets to the left of the arrow only load once assets to the right of the arrow have loaded. For assets like CSS files that have no dependencies, you can simply specify them in a quoted list, as shown.

More importantly, let's focus on defining the options for our application and the a7.init() function. As you can see in the options{} definition, altseven is divided into a number of packages- components, console, error, events, logging, model, remote, security, ui, and util. (auth is technically not a package but a function in the remote package, but it has a configuration option for session timeout).

Console is a package that renders as a floating window pane in the application. It displays a running list of entries from the log package. Optionally, it can also be configured to display a running list of log entries pushed from a web socket server. You can see a simple NodeJS socket server in my post Real-time server debug info using NodeJS, jQuery, Websockets and Logbox. I plan on coming back to this topic later, so feel free to ignore it for now. The gist of it is that you can include server-side logging information - or anything that can be pushed to a socket connection - in the console.

The logging package does just what it says- handles log statements in the application code. logLevel specifies what log levels from the code will be parsed and sent to the console.

The model package isn't listed in the options for this application, but it has a configuration option that can be used to set the model to any model package of your choosing. By default it uses the model in the gadget-ui library.

The remote package handles remote calls to the server. It has a built-in auth function that can be used with the built-in components.User object to provide authentication services against a server and set the application into secure mode. We're not using it in the tasklist application, but I will re-visit it later. Specify the application's remote modules using the modules option. The useTokens option can be set to true if you need an authenticated session with the server. It handles auth tokens that can be set by the server and passed back and forth for secure sessions.

The ui package handles ui templating and objects. It currently supports Mustache and Handlebars, but might include React support soon (hint hint). Load the Mustache library as part of your application to use Mustache, load Handlebars to use Handlebars.

Lastly for this post, the a7.init() call for altseven needs to be made inside a Promise. The a7.init() call resolves or rejects the promise and allows you to then proceed with running your application.

That's it for now. Next, I'll cover the client application code in more detail.