apper

Plug and play, restful, real-time application framework for single page apps

Usage no npm install needed!

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

README

Apper

Plug and play, restful, real-time application framework for single page apps.

Build Status on Travis CI NPM Latest version

App

A regular directory becomes a valid app if it has one or more of the following:

  • An environment settings module with the name environment.js
  • A middleware module with the name middleware.js
  • A socket subscription module with the name sockets.js
  • A static content directory with the name public
  • A routes module with the name routes.js

All of the default file/directory names above can be changed by options in apper.json file in any directory.

Install

For usage as command-line tool:

$ npm install -g apper

For usage in code:

$ npm install apper

Usage

From command-line

Just open the shell (or command prompt) in the app directory (with one or more of the above-mentioned files), and run:

$ apper

And open http://localhost:8000 in browser.

Run apper -h for all the command-line options.

A more specific deployment might look like:

$ apper --port 8080 --address 127.0.0.0 code/src

Here, code/src is the root path of the application code, relative to current directory from which apper is run. It defaults to the current directory itself.

To display internal logs during development (helpful), just set DEBUG environment variable to apper:*

$ DEBUG=apper:* apper --port 8000

For a working example, check out test/sample which is used by the tests. Just run apper in the shell there and read the tests to get a better understanding.

Motivation

[Express] 5 isn't enough. It lacks structure and conventions.

Apper provides:

  • Much needed structure to server-side code with strong conventions
  • Reliable directory hierarchy for code based on REST end-points
  • Design for real-time right off the bat
  • Transparent minification & bundling for single page apps

The core idea of apper is to enable easy REST api based node.js apps, especially useful for Single Page Applications.

Apper lets you create bigger apps by using smaller independent chunks as subapps. Simply place individual subapps anywhere in the directory hierarchy, and they get exposed under a relative base URL.

Nested subapps are totally cool and highly encouraged. In fact, simply by moving a subapp directory to another directory updates the exposed relative URL of that subapp. No frills.

Example

Example directory structure

+ root/
    - server.js (See Programmatic Usage section below)
    - routes.js (GET /login)
    + public/
        - index.html
        - main.js

    + api/
        - routes.js (GET /, POST /)
    
        + items/
            - middleware.js
            - routes.js (GET /, GET /last)

routes.js files above are supposed to specify route handlers for paths mentioned against them.

Read below for code samples for all of them.

API exposed by the directory structure above

GET  /
GET  /login

GET  /api
POST /api

GET  /api/items
GET  /api/items/last

The route / serves index.html in public/ by default. Can be overridden by including a route GET / in routes.js.

Core Concepts

Order of initialization of modules

The following modules get initialized on the subapp in order:

  • environment.js to set environment settings using app.set (like Express)
  • middleware.js to setup middleware functions using app.use (like Express)
  • sockets.js to setup WebSocket subscriptions between server and client
  • public exposed as static content directory
  • routes.js to respond to URL end-points (using app.get, app.post, etc.)

Bigger apps composed of small apps

The root app can contain sub-directories which are complete apps unto themselves. These directories become subapps of the root app (or sub APIs, if you may).

Subapps can be pulled out and placed anywhere in the overall directory structure. This would make them available on the new relative url with respect to the root.

Every subapp directory can be started as a separated app just by running apper in there.

Due to the directory hierarchy based mounting of subapps, the base URL paths of all subapps are decided by their position in the directory hierarchy.

The subapps can then register for any relative URL route after their base URL and handle requests accordingly.

Programmatic Usage

As a module

Create a file (say, server.js) in your application directory

var app = require('apper')({
    port: 8000
});
app.start();

Then, running server.js will start the application on port 8000. For example:

$ node server.js

To see internal logs (helpful during development), just set the environment variable DEBUG as follows:

$ DEBUG=apper:* node server.js

As Express application

Create an application object as usual, and use app.expressApp as a regular express application

var app = require('apper')();
app.expressApp.listen(5000);

As Express middleware

You can use this application as middleware in your regular express app as follows:

var app = require('apper')();
myExpressApp.use(app.expressApp);

Mounting your apper app as an Express subapp is as easy:

var app = require('apper')();
myExpressApp.use('/blog', app.expressApp);

Now the apper application gets confined to /blog base URL.

More Code

Startup

Apper synchronously loads its modules when you initialize it. You can start the server by calling app.start().

Constructor options look like this:

var app = require('apper')({
    path: '.',
    port: 8000,
    host: '0.0.0.0',
    
    // Not commonly used. Just use `apper.json` for the configuration
    toOpenBrowser: false,
    staticDir: 'public',
    moduleNames: {
        environment: 'environment'
        middleware: 'middleware',
        routes: 'routes',
        sockets: 'sockets'
    },
    mountPath: ''
});
app.start();

The default values for the options (path/port/etc) are as shown above. The options mean the following:

  • path: Path for the directory to be taken as the root application.
  • port: Port number on which to expose the application.
  • host: Host name to be used for the application (127.0.0.1, localhost, 0.0.0.0, etc).
  • toOpenBrowser: Whether to open the system default browser with the created application.
  • staticDir: Name of the static content directory.
  • moduleNames.*: As discussed below in Structure of Modules.
  • mountPath: Base URL to mount this application on. Used internally for mounting subapps.

Server automatically starts a single socket.io WebSocket server that works across all subapps but maintains separate namespaces for all communication with different subapps.

Structure of modules

Get an Express-based app object and run express methods like app.set, app.use, app.get, app.post, etc. on it.

For WebSocket requests, app.sockets provides the same functionality as io.sockets using [socket.io] 6

environment.js

module.exports = function (app) {
    app.set('property', 'value');
    // Environment configuration
};

middleware.js

module.exports = function (app) {
    
    app.use(function (req, res, next) {
        // middleware code
        next();
    });
};

routes.js

module.exports = function (app) {
    
    app.get('/', function (req, res) {
        res.send('hey');
    });
};

sockets.js

module.exports = function (app) {
    app.sockets.on('connection', function (socket) {
        
        socket.on('hey', function (name) {
            socket.emit('Hey ' + name + '!');
        });
        
    }
};

Client-side socket code

Client-side code corresponding to sockets.js looks as simple.

Just include <script src='/socket.io/socket.io.js'></script> in public/index.html, and connect to the socket server like this:

var socket = io();

socket.on('connect', function () {
    alert('woohoo!');
});

A subapp client connects by default to its own namespace, as per its directory hierarchy. So you won't have 2 different subapps catching each others socket events.

Configuration

apper.json placed in root or any subapp directory controls the following configuration for the respective app:

  • moduleNames.environment (Example: 'env')

    Environment module file name for the current app

  • moduleNames.middleware (Example: 'mid')

    Middleware module file name for the current app

  • moduleNames.sockets (Example: 'sock')

    Socket subscriptions module for the current app

  • moduleNames.routes (Example: 'route-definitions')

    Routes module file name for the current app

  • staticDir (Example: 'www')

    Static content directory name for the current app.

  • dirToIgnore (Example: ['subapp', 'another'])

    List of directories to not consider as subapps in the current app's directory.

  • bundle (Example: true/false)

    Whether to transparently minify and inline all JavaScript and CSS resources, including RequireJS modules. Cached on first use, and served as is thereon.

    You can include require-config.js in RequireJS baseUrl directory to specify custom RequireJS options for bundling. Usually not required.

Sample apper.json configuration file

{
    "moduleNames: {
        "environment": "env",
        "middleware": "mid",
        "sockets": "sock",
        "routes": "route-definitions"
    },
    "staticDir": "www",
    "dirToIgnore": ["subapp", "another"],
    "bundle": true
}

Tests

To run tests yourself, install mocha

$ npm install
$ npm install -g mocha

In the project directory, run

$ npm test

Check out the test directory for usage examples.

License

MIT