Flitter

Flitter is a quick & lightweight web application framework, created by Garrett Mills.

Welcome to Flitter!

flitter-big.png

What?

Flitter is an MVC style framework that aims to get you up and running faster by providing a structure and a wrapper for Express.js. Files in predictable directories are parsed into routes, middleware, controllers, models, and views.

Flitter provides access to the Express app, while making it possible to create an app without needing to piece together the Express framework.

Flitter is libre and open-source, and is developed over on my Gitea instance.

Documentation has moved.

The documentation for Flitter has moved. You probably want to be here: https://flitter.glmdev.tech/

Miscellaneous Info

Documentation has moved.

The documentation for Flitter has moved. You probably want to be here: https://flitter.glmdev.tech/

Error Philosophy

When errors occur in Flitter, be they code or user, they should be handled and passed up to the next highest level so as to be as non-disruptive and transparent as possible. Errors should be handled as appropriate at the lowest level possible. For example, server errors that don't affect the user should not disrupt the request-response flow, but should be logged in the console. If a sub-process of a controller fails, an error should be shown to the user, but the request shouldn't fail to send.

Bug Reports & Security Issues

Found a bug in Flitter? Please let me know! I'll try to get them fixed as soon as possible. You can file general bug reports as issues in the respective Gitea repositories here. If you're not sure which package to file your report under, just put it in the main Flitter repository. Flitter is libre, so any contributions are welcome!

Security bugs should be reported to security@glmdev.tech.

Releases

Flitter's sub-components are semantically versioned. This means that, beyond version 1.0.0, all releases of the same major release number are backwards-compatible. This documentation defines the public API of said components.

The main Flitter framework is developed with a different philosophy. Occasional versioned releases may be made, but to the best of my ability, the master branch of the Flitter repository should always have the most up-to-date working version of the framework. I always push a yarn.lock file with the framework that specifies the exact versions of the packages I was running.

NPM? Yarn.

I prefer Yarn as opposed to the Node Package Manager. It's effectively the same, but it works more reliably for me, and has little niceties that I prefer. As such, I use Yarn when developing Flitter. That means that, while you can install Flitter's packages with NPM, you won't have the benefit of a lock file. If you install packages with Yarn, you'll get the exact versions I had when I pushed the working framework.

Flitter, and any sub-components developed by me are Copyright © 2019 Garrett Mills.

Flitter and the aforementioned sub-components are released under the terms of the MIT license. They are subject to all terms within that license, available here. However, the MIT license is very permissive, which means you can basically use Flitter however you want, with no warranty provided by me, nor can I be held liable for their employment.

Adding Custom App Modifications

Documentation has moved.

The documentation for Flitter has moved. You probably want to be here: https://flitter.glmdev.tech/

From time to time, it may be useful for you (the developer) to add custom functionality to Flitter during initialization. This may be custom Express add-ons, tweaking settings of the underlying Express app, or any number of other things. As such, Flitter comes with a MiscUnit for these customizations. This unit in the file app/MiscUnit.js and follows the exact same structure as every other unit. It is loaded after the core Flitter units, but before any secondary or custom units. See libflitter/Unit for more information on how to add functionality to the unit.

Example: Adding the express-graphql Middleware

As an example, we'll configure the underlying Express app to use the express-graphql package on the route /graphql. To do this, modify the app/MiscUnit.js file:

const Unit = require('libflitter/Unit')

class MiscUnit extends Unit {
    
    constructor(){
        super()
        
        
    }
   
    go(app, next){
      	// Get the GraphQL package
        const graphqlHTTP = require('express-graphql')
        
        // Tell the underlying express middleware to use it
        app.use('/graphql', graphqlHTTP({
            schema: MyGraphQLSchema,
            graphiql: true
        }))
        
        next()
    }
    
    provides() {
        return {
            name: "misc"
        }
    }

}

module.exports = exports = MiscUnit

Here, we modify the go() method. This method is passed an instance of the underlying Express application (instance of express), which we can then use it as always.

Getting to Know Flitter

Getting to Know Flitter

Get Set Up

Documentation has moved.

The documentation for Flitter has moved. You probably want to be here: https://flitter.glmdev.tech/

Flitter runs on Node.js and is developed over on my Git server. Flitter and its sub-components developed by Garrett Mills are licensed under the highly permissive MIT License. In not so many words, this means that you can do basically whatever you want with the Flitter code as long as you absolve me from liability and warranty. Check out the full license here though.The master branch always contains a working version of Flitter, so getting started is as easy as cloning the main Flitter repository, installing the Node.js packages, and copying over the default configuration.

1. Clone the Repository

First, pull down a copy of Flitter into a new folder for your project. You can download a zip of the repository here, but the easiest way is to use Git:

git clone https://git.glmdev.tech/flitter/flitter <project_name>

This will create a new directory called <project_name> and download Flitter into it.

2. Install the Packages

Flitter relies on a lot of awesome Node.js packages to run. These can be installed using some flavor of Node.js package manager, which are available on basically every platform. Flitter recommends Yarn as a good option here. In fact, Flitter's creator uses Yarn, so a stable yarn.lock file is included with Flitter. Install the packages by running the following command from inside the project folder:

yarn install

3. Copy the Default Config

Flitter uses .env based environment configuration, which we'll cover more in-depth later, but for now, just copy over the included example configuration:

cp example.env .env

4. Start Flitter!

That's all it takes to get Flitter ready to run. You can start the development server using the ./flitter command:

./flitter up

That should start Flitter running on port 8000 by default.

Getting to Know Flitter

Step 0: Configuration

Documentation has moved.

The documentation for Flitter has moved. You probably want to be here: https://flitter.glmdev.tech/

It's important to get your configuration settings right when using Flitter. Flitter behaves differently in development mode than it does running in production -- this includes exposing error messages and stack traces that could reveal sensitive information about your application. In this section, we'll take a look at how Flitter handles configuration, and the best way to customize it.

Flitter Config: A Two-Layer Approach

Configuration in Flitter is loaded in two steps. Environment-specific config is done by changing values in a .env file in the root of the application. This file is never, ever committed to version control and should not be published with your app. It includes things like the database credentials, crypto key, and environment settings.

Flitter ships with a sample example.env file that you can customize. Just cp example.env .env and fill in your information. It's important that you replace the SECRET variable with a random value, as it's used by Flitter to encrypt things like password hashes. Here's a sample:

SERVER_PORT=8000
LOGGING_LEVEL=1

DATABASE_HOST=127.0.0.1
DATABASE_PORT=27017
DATABASE_NAME=flitter
DATABASE_AUTH=true
DATABASE_USER=flitter
DATABASE_PASS=flitter

SECRET=changeme
ENVIRONMENT=development

All other configuration is defined in .config.js files in the config/ folder. Here's a sample server.config.js:

const server_config = {

    port: process.env.SERVER_PORT || 80,
    environment: process.env.ENVIRONMENT || "production",
    logging: {
        level: process.env.LOGGING_LEVEL || 1
    },
    session: {
        secret: process.env.SECRET || "changeme"
    }

}

module.exports = server_config

All of the process.env bits are references to values that are specified in the .env file, but they have defaults, just in case you forget to specify the environment variables. When the framework starts, these configuration files are loaded and are stored in memory based on the name of the file. Then, you can access them globally using the config() function. Note that the name of the file defines the name of the configuration, not the actual variable name. This is true of most things with Flitter. Here's an example:

Note that the sample configuration file was called server.config.js, so we access the configuration called server.

(flitter)> config('server')

// returns:
{ port: 80,
  environment: 'production',
  logging: { level: 1 },
  session: { secret: 'changeme' } }

(flitter)> config('server.port')

// returns:
80

Flitter takes into account sub-directories when naming configuration as well. Say you had a configuration file config/auth/registration.config.js. Flitter would note the auth/ sub-directory, and you would access this configuration using config('auth:registration').

The use of the : character is an important Flitter convention. Flitter parses reference names from file paths relative to their base directory. When referencing folders, use the : character to separate them. For instance, auth/login/error becomes auth:login:error.

Creating Custom Configuration Files

You can add custom configuration files to your application, and they will be loaded by Flitter and made available globally like any other configuration file. To create a new config file, you can use ./flitter, Flitter's development CLI tool, to create a new configuration file from the built-in template:

./flitter new config subdirectory/file_name

This will generate the following file, config/subdirectory/file_name.config.js:

// file_name Configuration
const file_name = {

    // variable: process.env.VARIABLE || 'default value',

}

module.exports = file_name

Now you can specify the configuration values you want, then the configuration can be referenced from config('subdirectory:file_name').

Getting to Know Flitter

Step 1: Routing

Defining Routes

Flitter routes are defined within files inside the app/routing/routers/ folder. For those familiar with MVC frameworks, each file is a route group (which can have its own prefix), and middleware can be applied to individual routes, groups, or globally.

Flitter comes with a default routes file, app/routing/routers/index.routes.js which shows the structure of a Flitter routes file:

const index = {
    prefix: '/',
    middleware: [
        mw('HomeLogger'),
    ],
    get: {
        '/': [ controller('Home').welcome ],
    },
    post: {

    },
}

module.exports = index
  • prefix defines the route prefix
    • e.g. if you define /home in the file with a prefix of /user, then the final route will be /user/home.
  • middleware is an array of middleware to be applied, in order, to all routes in the file.
    • Middleware should be referenced using the built-in mw() function. More on that later.
  • get defines routes for the GET method, and post for the POST method
    • These should be objects where the key is the route's URI, and the value is an array of functions to be applied, in order, when the route is handled. Route-specific middleware should be included here, also using mw().

When Flitter starts, it loads each routes file and creates an Express Router for each one, with the specified prefix. Group middleware is applied to the router, and then each route is registered.

Creating Custom Route Files

Custom route files can be created and placed in the app/routing/routers/ folder. As long as they conform to the form required, they will be loaded by Flitter when the application is started. You can create a new route file from the template using a ./flitter command:

./flitter new router file_name

Which will create the file routing/routers/file_name.routes.js. Currently, Flitter does not support sub-directories for route definitions. This means that route files placed in sub-directories within routing/routers/ will not be loaded by Flitter.

Middleware

Middleware in Flitter is defined in the app/routing/middleware/ directory. The middleware's class should contain a method test() which takes 3 variables: the Express request, the Express response, and the function to be called to continue execution of the Flitter stack. Here's an example:

class RequireAuth {
    test(req, res, next){
        if ( req.session.auth.user ) ){
            // call the next function in the stack
            next()
        }
        else {
            req.session.destination = req.originalUrl
            return res.redirect('/auth/login')
        }
    }
}

module.exports = RequireAuth

Here, if there is a user loaded in the session (which would mean that the user is logged in), then we allow the request to continue. Otherwise, we redirect to the login page. The next() call is very important because it keeps the chain of function calls that execute the middleware running.

Using Middleware

Middleware can be applied in three ways: globally, per group, or per route. Global middleware is applied to every request Flitter handles, group middleware is applied to all routes in a given routes file, and route middleware is applied to a single route.

Middleware loaded by Flitter should be accessed using the global mw() function. This function takes the middleware name as an argument and returns the test function that is applied to the request. Middleware names follow Flitter's convention for sub-directories. So, if you have a middleware file app/routing/middleware/auth/RequireAuth.middleware.js, it can be accessed through the mw() function like so: mw('auth:RequireAuth').

Global Middleware

Middleware can be applied globally by adding it to the array of middleware in app/routing/Middleware.js. The middleware in this file is applied in order to every request Flitter handles. An example:

// app/routing/Middleware.js
const Middleware = [
    mw('RouteLogger'),
    mw('RateLimiter'),
]

module.exports = exports = Middleware

Here, the RouteLogger middleware is applied, then the RateLimiter middleware is applied. These will execute in that order for every request.

Group Middleware

Middleware can be applied to all routes in a group. In Flitter, each routes file is a group. Therefore, middleware can be applied to all routes in a given file by adding it to the middleware array. For example:

// app/routing/routers/index.routes.js
const index = {
    prefix: '/',
    middleware: [
        mw('HomeLogger'),
    ],
    get: {
        '/': [ controller('Home').welcome ],
    },
}

module.exports = index

Here, the HomeLogger middleware will be applied, in order, to all routes specified in the file, regardless of request method.

Route Middleware

Finally, middleware can be applied to individual routes by adding the middleware to the array of functions in a given routes file. The functions in the array will be executed in order to handle the route. For example:

// app/routing/routers/index.routes.js
const index = {
    prefix: '/',
    middleware: [],
    get: {
        '/': [ mw('HomeLogger'), controller('Home').welcome ],
    },
}

module.exports = index

Here, the HomeLogger middleware will be applied to the / route before its handler is executed.

Creating Custom Middleware

Custom middleware files can be created and placed in the app/routing/middleware/ folder. As long as they conform to the form required, they will be loaded by Flitter when the application is started, and they can be accessed by name via the built-in mw() function. You can create a new middleware file from the template using a ./flitter command:

./flitter new middleware subdirectory/file_name

Which will create the file app/routing/routers/subdirectory/file_name.routes.js:

/*
 * file_name Middleware
 * -------------------------------------------------------------
 * Put some description here!
 */
class file_name {

    /*
     * Run the middleware test.
     * This method is required by all Flitter middleware.
     * It should either call the next function in the stack,
     * or it should handle the response accordingly.
     */
    test(req, res, next){
        console.log("Do stuff here!")

        /*
         * Call the next function in the stack.
         */
        next()
    }
}

module.exports = file_name
Getting to Know Flitter

Step 2: Views & Static Assets

Flitter uses Pug as a view engine, so, for the most part, working with views is a standard experience.

Creating Views

Creating views in Flitter is identical to any other Pug view. You can find more info on that here. Views should be placed in the views/ directory and follow the Flitter standard convention for sub-directories, so views/auth/login.pug can be accessed as auth:login.

Serving Views

Flitter makes a global function view() available for use in controller methods. To serve a view as a response, simple return the view() function with the name of the view. View names follow the standard Flitter convention for sub-directories. Here's an example of a controller method that serves a view:

// app/controllers/Auth.controller.js
class AuthController {
	login(req, res){
        return view(res, 'auth:login', {destination: req.session.destination})
    }
}

module.exports = AuthController

Here, the login() method returns the pug view located at views/auth/login.pug. The first argument is the response object. The second argument is the Flitter name of the view. The optional third argument is an object of variables to be passed to the view. The object keys correspond to the variable names in the template. That is { username: "foobar" } can be accessed as {{ username }} in the template.

Static Assets

Flitter serves static assets from the app/assets/ directory. All files in this folder are made publicly available using the /assets prefix. For example, the file app/assets/flitter.png can be accessed at http://application.url/assets/flitter.png. Flitter serves the app/assets/favicon.ico file specially using the express-favicon plugin automatically. That means you can just replace the app/assets/favicon.ico file with a custom one for your app and Flitter will serve it automatically.

Getting to Know Flitter

Step 3: Models & the Database

Flitter uses Mongoose to provide MongoDB models. Model schemas are defined in files and loaded into Mongoose when Flitter initializes. MongoDB connection info is specified in the environment configuration.

Defining Model Schemata

Side Note:
schemata (n. pl.) - plural of schema

Model schemata define the structure and fields of the model. The schemata are passed directly to Mongoose, so for the most part, you can just reference the Mongoose docs for how to create a schema. The schemata should be defined in individual files in the app/models/ directory. Each of these files should return the object-definition of the Mongoose schema. Here's an example:

// models/auth/User.js
const User = {
    username: String,
    password: String,
    uuid: String,
}

module.exports = User

Custom model definitions can be created and added to the app/models/ directory. You can use the ./flitter command to generate the model with any given name. Model names are parsed from the file name and follow the standard Flitter convention for sub-directory naming. For example:

./flitter new model subdirectory/file_name

This will generate the file app/models/subdirectory/file_name.model.js, which can be referenced as subdirectory:file_name:

/*
 * file_name Model
 * -------------------------------------------------------------
 * Put some description here!
 */
const file_name = {
    field_name: String,
}

module.exports = exports = file_name

Using Models

When Flitter starts, it loads each of the schemata and creates Mongoose models. These models are made available globally via the model() function. Flitter model names are parsed from the file name and follow the standard Flitter convention for sub-directory naming. Interacting with these models is simply interacting with normal Mongoose models, for which you can refer to the excellent Mongoose docs. Here's an example of using the global function to use a model:

// excerpt from app/controllers/Auth.controller.js
login_post( req, res ){
  		// Get the User model.
        const User = model('auth:User')
        
        // Interact with it as you normally would.
        User.findOne({username: req.body.username}, (err, user) => {
            if ( !user ){
                create_error_session('username', 'Invalid username.')
                return res.redirect('/auth/login')
            }
            
            bcrypt.compare( req.body.password, user.password, (err, match) => {
                
                if ( !match ){
                    create_error_session('password', 'Invalid password.')
                    return res.redirect('/auth/login')
                }
                else {
                    
                    controller('Auth').create_auth_session(req, user)
                    
                    return res.redirect('/auth/dash')
                }
            })
        })
    }
Getting to Know Flitter

Step 4: Controllers

Controllers are the center of logic in Flitter. Routes should use controller methods to handle requests, and the controller methods should do all of the actual logic to process said requests. Controllers are defined in the controllers/ directory, and are made available globally via the controller() function.

Creating Controllers

Controllers are defined in the app/controllers/ directory and have the .controller.js extension. Controller names are parsed from the file names and follow the standard Flitter convention for sub-directory naming. Controller classes are just normal classes with methods that take the Express request and response objects and should send a response back. Here's an example controller file:

// app/controllers/Home.controller.js
class Home {
    welcome(req, res){
        return view(res, 'welcome')
    }
  	error(req, res){
      	const error_string = "Uh, oh! Error."
      	return res.send(error_string)
    }
}

module.exports = Home

Controller classes are allowed to contain whatever methods or class variables that you want. However, methods that will be used as handlers for routes should accept the Express request and response objects as arguments and should send a response at some point, then return the status of that sending to Flitter. The two main ways this can be done are directly, using the response object (see the Express docs) or using the global view() function.

New controller files can be created from Flitter's template using the ./flitter command, like so:

./flitter new controller subdirectory/file_name

This command will create the file app/controllers/subdirectory/file_name.controller.js which can be referenced as subdirectory:file_name:

/*
 * file_name Controller
 * -------------------------------------------------------------
 * Put some description here!
 */
class file_name {

    /*
     * Serve the main page.
     */
    main(req, res){

        /*
         * Return the main view.
         * It must be passed the response.
         * View parameters can be passed as an optional third
         * argument to the view() method.
         */
        return view(res, 'view_name')
    }
}

module.exports = exports = file_name

Using Controllers

Route files should not contain any actual logic. Instead, they should simply list middleware to be applied to a route, and then reference a method on a controller that should contain the logic for that route. Flitter makes available a global controller() function that returns instances of the defined controllers by name. Controller names are based on the file name and follow the Flitter convention for sub-directory naming. Here's an example:

// app/routing/routes/index.routes.js
const index = {
    prefix: '/',
    middleware: [
        mw('HomeLogger'),
    ],
    get: {
        '/': [ controller('Home').welcome ],
    },
}

module.exports = index

Here, the controller('Home') call gets an instance of the controller file app/controllers/Home.controller.js and tells Flitter to use the welcome method to handle the / route. It's important to note that the reference does not call the actual function, but just references the name. Flitter will call the function when it receives a request.

That is, this is wrong: controller('Home').welcome()
But this is correct: controller('Home').welcome

Getting to Know Flitter

The ./flitter Command

Documentation has moved.

The documentation for Flitter has moved. You probably want to be here: https://flitter.glmdev.tech/

By default, Flitter ships with the flitter-cli package, which enables use of the ./flitter command. This command is designed to aid development on Flitter. This command should only be run from the root of the application, as it depends on relative paths.

Launch a Development Server
./flitter up

While not designed for use in production, ./flitter can start a Flitter server quickly for testing your app.

Run a Deployment
./flitter deploy <deployment name>

Flitter units can define special functions called deployments. These deployments are designed to be one-time, non-reversible functions that set up the unit's necessary files and configuration, etc. For example, the flitter-auth package provides the auth deployment which creates the controllers, models, views, and middleware to enable flitter-auth.

Create a New File From a Template
./flitter new <template name> <file name>

Flitter units can also define templates for files that are used regularly in Flitter. For example, libflitter provides several templates like the controller template, which creates a new controller with the given <file name> in the base controllers/ directory.

Launch the Flitter Shell
./flitter shell

Sometimes, when you're developing a feature for your app, it's useful to be able to test things out and interact with Flitter directly, without having to interact with Flitter through a web-browser. As such, this command will start an interactive prompt that has access to the entire Flitter context. It behaves just like an interactive Node prompt, but it is started from within Flitter, so you can access all the same resources that your app can directly.

Getting to Know Flitter

Production

While the ./flitter provides a command to start the Flitter server, it's recommended that you use the index.js file to start the production server. This file will start the Flitter server and can be started by running:

node index.js

This is especially helpful for running Flitter as a service, like with systemd:

[Unit]
Description=MyApp - my app built on Flitter
Documentation=http://app.url/
After=network.target

[Service]
Type=simple
User=flitter-user
WorkingDirectory=/path/to/flitter
ExecStart=/usr/bin/env node /path/to/flitter/index.js
Restart=on-failure

[Install]
WantedBy=multi-user.target

libflitter

libflitter

Overview

libflitter is the core package that runs Flitter. Flitter's initialization structure is broken into individual bits called units, and these units are chained one after the other to create the initialization chain (usually referred to as the stack or the chain).

Each unit contains a function that, when called, initializes that particular unit. When forming the stack, Flitter passes each unit's function to the one before it. When it finishes initializing itself, the unit should call that function from within whatever scope it initializes. In Flitter, these are called contexts. The concept of contexts is important to understanding how Flitter works. Each unit of the initialization chain creates a context and the rest of the chain continues from within that context, and therefore has access to the resources that context exposes.

For example, the first unit to start is the GlobalUnit. This unit creates the global _flit object with various resources, then it calls the function of the next unit in the series. From that point on, all other units have access to the _flit object.

Another example is the DatabaseUnit. The database unit reads the configured model schemata and creates Mongoose models from them. Then, it uses Mongoose to connect to the configured MongoDB connection. It calls the function of the next unit in the series from within the callback of the Mongoose connect function -- this is its context. That means that all other units loaded after that are guaranteed access to the database -- they run from within (a.k.a. under) the database context.

libflitter contains the units that comprise the core functionality of Flitter, as well as the base classes for things like the FlitterApp and Unit files. In order, libflitter provides the following units:

  • GlobalUnit
  • ConfigUnit
  • UtilityUnit
  • ExpressUnit
  • ViewEngineUnit
  • DatabaseUnit
  • MiddlewareUnit
  • RoutingUnit
  • StaticUnit
  • ErrorUnit
  • AppUnit

In this chapter, we'll go through the different units provided in libflitter, as well as the API they provide.

libflitter

The Almighty, the All-Powerful, the Flitter App

libflitter/app/FlitterApp

exports:  class FlitterApp

class FlitterApp

This is the absolute core of Flitter. This class contains the logic to create the underlying Express install, load the Units, chain them together, then run the initialization stack.

app

type:  express

The underlying Express application instance.

units

type:  Object

An Object of key-value pairs where the key is the name of the Unit and the value is an instance of the Unit class (should be libflitter/Unit). These units are chained in order such that each Unit has access to the context of those preceding it.

constructor(Object unit_classes)

returns:  this

Initializes the class. Stores the provided unit_classes in this.units. Creates a new Express app and stores it in this.app.

up()

returns:  undefined

Chains the units in this.units in order, passing each the function to start the next. For each unit, it processes the provides() output and stores it in _flit. Then, it calls the first unit in the chain to start initializing Flitter.

libflitter

Unit

libflitter/Unit

exports:  class Unit

class Unit

The base class for the Flitter Unit. The Unit is the building block of the Flitter framework. Functionality in Flitter is broken into Units which are specified in order when Flitter is loaded. They contain a function which is used to initialize the unit. This function is passed the function of the next unit in the stack. Linked together as such, the collection of units forms the initialization stack of Flitter. Each unit establishes what's known as a context. A unit's context is just a point in its code when it has loaded all the functionality it provides to other units. From this point, it should call the next unit in the stack, giving it access to the functionality provided.

go(express app, function next)

returns:  undefined

This function initializes the unit. It should do everything required to establish the unit's context. Unlike the rest of the unit, when it is called it has access to all of the contexts above it. When it finishes starting up the unit, it should call the next function in the stack from within a scope that has access to all the resources this unit provides (its "context"). It is passed an instance of the underlying Express app so it can add or modify any functionality.

provides()

returns:  Object

Describes the resources provided by the unit. While optional, if the unit provides resources that other units may rely on, they should be specified here. This includes the service name of the unit, any directories it uses, and any templates it creates. These should be returned as an Object with particular formatting. Here's an example:

    provides() {
        return {
          	// The name of the service provided by the unit. Should be a String.
            name: "database",
          	
          	// List of fully-qualified directories provided by the unit.
          	// These directories are available by their assigned names.
            directories: {
                models: this.directory
            },
          	
          	// Templates provided by the unit.
            templates: {
              
              	// The key is the name of the template.
                model: {
                  	// This should be a function that takes (String name) and returns the text of the template.
                    template: require('libflitter/templates/model'),
                  	
                  	// Destination directory for the newly generated file.
                    directory: this.directory,
                  
                  	// Extension of the newly generated file.
                    extension: '.model.js'
                }
            }
        }
    }
libflitter

The Global Unit

libflitter/global/GlobalUnit

exports:  class GlobalUnit

class GlobalUnit

extends:  libflitter/Unit

The GlobalUnit initializes the global _flit object, which contains stores for things like templates, directories, and service names, as well as a number of convenience functions. It is also the recommended way for custom Flitter units to expose global variables and functions.

go(express app, function next)

overrides:  super.go()         returns:  undefined

This function initializes GlobalUnit, as required by the parent class. It creates the global _flit object and executes the next() unit in the chain from within its context. Provides the Global context.

Global Context

This context provides access to _flit.

global._flit

type:  Object

Contains stores for things like templates, directories, and service names, as well as a number of convenience functions. It is also the recommended way for custom Flitter units to expose global variables and functions.

directories

type:  libflitter/utility/DirectoryStore

Provides access to globally-registered Flitter directories - these are provided and used by various units, including custom ones, to access directories that can be changed by the user programmatically.

deployments

type:  libflitter/utility/DeployStore

Provides access to globally-registered Flitter deployments - these are provided and used by various units, including custom ones, to run deployments to set up various units.

templates

type:  libflitter/utility/TemplateStore

Provides access to globally-registered Flitter templates - these are provided and used by various units, including custom ones, to create various types of files to aid application development.

services

type:  Array

A list of service names of type String. If a unit provides a service that other units or the developer are meant to interact with, they should push a service name here. This allows other units and the application to check if a service is provided.

has(String name)

returns:  Boolean

Checks if the given name is in the this.services. This is how other units and the application can check if a given unit has been loaded.

libflitter

The Config Unit

libflitter/config/ConfigUnit

exports:  class ConfigUnit

class ConfigUnit

extends:  libflitter/Unit

The ConfigUnit initializes the dotenv package and loads config files from the specified directory, then makes them available under the Config context.

constructor(String config_directory = './config')

overrides:  super.constructor()         returns:  this

Creates an instance of the class. Also resolves the provided config_directory using the path package and stores it in this.directory.

go(express app, function next)

overrides:  super.go()         returns:  undefined

Initializes the dotenv package. Then, it recursively iterates over every file in this.directory and parses the name from the file, then imports that file and stores its config under that name. Then, it creates a global function to access the configuration by name and calls the next unit in the stack from within the Config context.

provides()

overrides:  super.provides()         returns:  Object

Returns an Object describing the services, templates, and directories provided by this unit. They are as follows:

Type Key Value Notes
Service Name name "config" The name of the service. This is added to the _flit.services array so other units can check for this unit.
Directory directories.config this.directory The fully-qualified path of the config files. This is added to the _flit.directories store so other units can access it.
Template templates.config Object Template that creates new config file in this.directory.

directory

type:  String

Set on initialization. Specifies the fully-qualified path of the configuration files to be processed. Each of these files should export an Object of key-value pairs that hold the configuration settings for that file. Each file's configuration is assigned a name based on the name of the file it was retrieved from. This includes sub-directories. As is convention in Flitter, sub-directories are delineated with a : character. So a config loaded from the file auth/Login.config.js would be named auth:Login.

Config Context

This context provides access to the configuration imported from the environment and config files. They can be accessed via the global config() function.

_flit.config

type:  Object

Stores the raw config objects, as well as the access function.

d

type: Object

Stores the raw config data imported from the configuration files. The configs are stored in key-value pairs where the key is the parsed name of the config, following the Flitter convention for sub-directory naming, and the values are the raw config Objects.

get(String accessor)

returns:  mixed

This function is used to access config values stored in _flit.config.d. The accessor string is a dot-separated reference to the config, which begins with the name of the config, which follows the Flitter standard convention for sub-directories. For example, the accessor auth:Messages.error.unavailable would retrieve the error.unavailable value from the file auth/Messages.config.js in the config directory.

global.config(String accessor)

Mapped to _flit.config.get.

libflitter

The Utility Unit

libflitter/utility/UtilityUnit

exports:  class UtilityUnit

class UtilityUnit

extends:  libflitter/Unit

The UtilityUnit is to add various useful functions to Flitter. It includes logging functions.

go(express app, function next)

overrides:  super.go()         returns:  undefined

Initializes the unit. It creates a logging function that changes whether or not a message prints to console based on the logging level and makes it globally available. It provides the Utility context and calls the next function in the stack under that context.

Utility Context

This context provides access to the various utility functions. At the moment, this is basically just the logging function.

global.log(mixed message, Number level = 1, Boolean strict = false)

returns:  undefined

Passes a message to console.log based on the specified logging level. As long as not strict, if the logging level specified in the config value server.logging.level is greater than the specified level, the message will be passed. If strict, the message will be passed to console.log if and only if the server.logging.level config value is exactly equal to the level.

libflitter

The Express Unit

libflitter/express/ExpressUnit

exports:  class ExpressUnit

class ExpressUnit

extends:  libflitter/Unit

Flitter is based on Express and various Express extensions. This unit loads the additional Express extensions like the session provider and form body parser.

go(express app, function next)

overrides:  super.go()         returns:  undefined

Initializes the unit. Loads the package busboy-body-parser and tells the underlying Express app to use it. It also loads the express-session package and configures it. Flitter sessions are configured to use UUIDs, and the session secret is loaded from the server.session.secret config value. Then, it tells the underlying Express app to use it. This unit provides the Express context and calls the next function in the stack under it.

Express Context

This context makes the busboy-body-parser and express-session packages available in the underlying Express app. This means that all Flitter forms should use the multipart type, but it also provides support for accessing submitted files via request.files.

libflitter

The View Engine Unit

libflitter/views/ViewEngineUnit

extends:  libflitter/Unit

Flitter uses Pug as a view engine. This unit configures the underlying Express app to use it and creates a global function to serve a view.

go(express app, function next)

overrides:  super.go()         returns:  undefined

Initializes the unit. Configures the underlying Express app to use the Pug view engine. Flitter doesn't change the way Express looks for views, so they should all go within the views/ directory. Also creates a function to serve up views by name and makes it globally available in the ViewEngine context, then it executes the next function in the stack under it.

ViewEngine Context

This context provides access to the global view() function and configures the underlying Express app to use Pug as a view engine.

global.view(express.response response, String view_name, Object args)

returns:  mixed

Looks up a view with the specified view_name and renders it. View names follow the Flitter convention for sub-directory naming, so the view name auth:Login will render the view file views/auth/Login.pug. Any key-value pairs passed in the args object are passed along to the view such that the keys become variable names with their respective values. For example, the args object { username: "johndoe" } would make the username variable available in the view, like so: {{ username }}. The response should be passed in from the function that calls this one. Calling this function will send a response as it invokes express.response.render().

libflitter

The Database Unit

libflitter/database/DatabaseUnit

exports:  class DatabaseUnit

class DatabaseUnit

extends:  libflitter/Unit

Creates Mongoose models from defined schemata and connects to the database.

constructor(String models_directory = './app/models')

overrides:  super.constructor()         returns:  this

Uses the path package to resolve the fully-qualified path to the models directory and stores it in this.directory, which is where model schemata should be placed. Creates a new instance of this.

go(express app, function next)

overrides:  super.go()         returns:  undefined

Recursively iterates over all files in this.directory and gets the model schemata from them. Then, creates Mongoose models for each schema using the name parsed from the file. Model names are parsed from the schema file name and follow the Flitter convention for sub-directory naming. For example, the schema file models/auth/User.model.js would create a model with the name auth:User. Creates global access functions for these models and schemata. Next, it tries to connect to the MongoDB using the credentials specified in the database config. The Database context is created here and the next function in the stack is called under it.

provides()

overrides:  super.provides()         returns:  Object

Returns an Object describing the services, templates, and directories provided by this unit. They are as follows:

Type Key Value Notes
Service Name name "database" The name of the service. This is added to the _flit.services array so other units can check for this unit.
Directory directories.models this.directory The fully-qualified path of the model schemata files. This is added to the _flit.directories store so other units can access it.
Template templates.model Object Template that creates new model schema file in this.directory.
directory

type:  String

Set on initialization. Specifies the fully-qualified path of the model schema files to be processed. Each of these files should export an Object of key-value pairs that comprise the Mongoose schema for the model to be created. Each file's schema is assigned a name based on the name of the file it was retrieved from. This includes sub-directories. As is convention in Flitter, sub-directories are delineated with a : character. So a schema loaded from the file auth/User.model.js would be named auth:Model.

Database Context

This context provides access to the imported schemata and their respective models, as well as a guaranteed-successful database connection.

_flit.orm

type:  Object

Holds variables and functions provided in this context.

_flit.orm.schemas

type:  Object

Contains the imported schemata in key-value pairs such that the keys are the parsed names of the schemata, which follow Flitter's convention for sub-directory naming, and the values are the Object instances that contain the schemata.

_flit.orm.models

type:  Object

Contains the Mongoose models created from the imported schemata in key-value pairs such that the keys are the parsed names of the models, which follow Flitter's convention for sub-directory naming, and the values are instances of mongoose.model.

_flit.orm.connect_string

type:  String

Contains the address used to connect to the MongoDB server. This is usually formatted:

mongodb://user:password@host:port/database

_flit.orm.schema(String name)

returns:  Object

Retrieves a schema with the specified name from _flit.orm.schemas and returns it.

_flit.orm.model(String name)

returns:  mongoose.model

Retrieves a model with the specified name from _flit.orm.models and returns it.

global.schema(String name)

returns:  Object

Mapped to _flit.orm.schema().

global.model(String name)

returns:  mongoose.model

Mapped to _flit.orm.model().

libflitter

The Middleware Unit

libflitter/middleware/MiddlewareUnit

exports:  class MiddlewareUnit

class MiddlewareUnit

extends:  libflitter/Unit

The MiddlewareUnit loads and registers middleware from the specified directory and applies global middleware. It makes the middleware globally available under the Middleware context.

constructor(String mw_path = './app/routing/middleware', String global_definitions_file = './app/routing/Middleware.js')

overrides:  super.constructor()         returns:  this

Initializes the class. Resolves the fully-qualified paths for the middleware definitions folder and the file which contains the array of middleware to be applied globally and stores these paths in this.directory and this.globals_file respectively.

go(express app, function next)

overrides:  super.constructor()         returns:  undefined

Recursively iterates over files in this.directory and loads the middleware definition classes from them and stores the collection by name in _flit.middleware.definitions. Then, it creates an access function and stores it in _flit.middleware.mw. Then, global middleware is loaded from the array in this.globals_file and is applied, in order, to the underlying Express app. This creates the Middleware context and the next function in the stack is called under it.

provides()

overrides:  super.provides()         returns:  Object

Returns an Object describing the services, templates, and directories provided by this unit. They are as follows:

Type Key Value Notes
Service Name name "middleware" The name of the service. This is added to the _flit.services array so other units can check for this unit.
Directory directories.middleware this.directory The fully-qualified path of the middleware definition files. This is added to the _flit.directories store so other units can access it.
Template templates.middleware Object Template that creates new middleware definition file in this.directory.
directory

type:  String

Set on initialization. Specifies the fully-qualified path of the middleware definition files to be processed. Each of these files should export a class with a test() function that runs the middleware. Each file's middleware is assigned a name based on the name of the file it was retrieved from. This includes sub-directories. As is convention in Flitter, sub-directories are delineated with a : character. So a middleware class loaded from the file auth/RequireAuth.middleware.js would be named auth:RequireAuth.

globals_file

type:  String

Set on initialization. Specifies the fully-qualified path of the global middleware file. This file should export an Array of references to middleware that will be applied to every request Flitter handles. These should be specified using the global mw() function.

Middleware Context

This context provides access to registered middleware and the global middleware access function. It also guarantees that that all requests will have the global middleware applied to them.

_flit.middleware

type:  Object

Stores variables and functions provided by this context.

_flit.middleware.definitions

type:  Object

A key-value collection of defined middleware where keys are parsed from the names of files in the specified middleware directory, following the Flitter convention for sub-directory naming. The values are classes containing a test() function that is applied as middleware.

_flit.middleware.mw(String name)

Returns the middleware class from _flit.middleware.definitions with the name provided.

global.mw(String name)

Mapped to _flit.middleware.mw().

libflitter

The Controller Unit

libflitter/controller/ControllerUnit

exports:  class ControllerUnit

class ControllerUnit

extends:  libflitter/Unit

Loads controllers from the specified directory and creates an access function for them. It makes these available as the Controller context and calls the next function in the stack under it.

constructor(String controller_directory = './app/controllers')

overrides:  super.constructor()         returns:  this

Initializes the class. Resolves the fully-qualified path from the provided controller_directory and stores it in this.directory.

go(express app, function next)

overrides:  super.go()         returns:  undefined

Iterates over files in this.directory and retrieves the controller classes from them. Stores them in _flit.controllers.definitions in key-value pairs, where the key is the name of the controller and the value is an instance of the class. The names are parsed from the file names and use Flitter's convention for sub-directory naming. Creates a function to access these instances and makes it available at _flit.controllers.controller(). These establish the Controller context and then the next function in the stack is called under it.

provides()

overrides:  super.provides()         returns:  Object

Returns an Object describing the services, templates, and directories provided by this unit. They are as follows:

Type Key Value Notes

Service Name

name

"controllers"

The name of the service. This is added to the _flit.services array so other units can check for this unit.
Directory directories.controllers this.directory The fully-qualified path of the config files. This is added to the _flit.directories store so other units can access it.
Template templates.controller Object Template that creates new config file in this.directory.
directory

type:  String

Set on initialization. Specifies the fully-qualified path of the controller class files to be processed. Each of these files should export a class of request-handling methods. Each file's class is assigned a name based on the name of the file it was retrieved from. This includes sub-directories. As is convention in Flitter, sub-directories are delineated with a : character. So a controller class loaded from the file auth/Login.controller.js would be named auth:Login.

Controller Context

This context provides access to instances of the controller classes by name, as well as the global access function, controller().

_flit.controllers

type:  Object

Holds the variables and functions provided by this unit.

_flit.controllers.definitions

type:  Object

A collection of key-value pairs such that they keys are the names of the controllers parsed from the files in the specified directory. These names follow the Flitter convention for sub-directory naming. The values are instances of the corresponding controller classes.

_flit.controllers.controller(String name)

returns:  mixed

Access function that returns the controller from _flit.controllers.definitions with the corresponding name.

global.controller(String name)

returns:  mixed

Mapped to _flit.controllers.controller().

libflitter

The Routing Unit

libflitter/routing/RoutingUnit.js

exports:  class RoutingUnit

class RoutingUnit

extends:  libflitter/Unit

Loads the route definition files from the specified and creates Express routers for each file, then registers them with the underlying Express app. Creates a global function to return a redirect response.

constructor(String router_directory = './app/routing/routers')

overrides:  super.constructor()         returns:  this

Initializes the class. Resolves the fully-qualified path of the router_directory and stores it in this.directory.

go(express app, function next)

overrides:  super.go()         returns:  undefined

Creates an alias function to make returning redirections easier. Then, it reads the files in this.directory, non-recursively and loads the routes defined in each valid file. Using these routes, it creates an Express router for each file and registers it with the underlying Express app. This establishes the Routing context and calls the next function in the stack under it.

provides()

overrides:  super.provides()         returns:  Object

Returns an Object describing the services, templates, and directories provided by this unit. They are as follows:

Type Key Value Notes
Service Name name "routing" The name of the service. This is added to the _flit.services array so other units can check for this unit.
Directory directories.routers this.directory The fully-qualified path of the router files. This is added to the _flit.directories store so other units can access it.
Template templates.router Object Template that creates new config file in this.directory.
directory

type:  String

Set on initialization. Specifies the fully-qualified path of the route definition files to be processed. Each of these files should export an Object of key-value pairs that hold the route definition information for that file. Each file's configuration is assigned a name based on the name of the file it was retrieved from. Unlike other units, this does not include sub-directories. Only files in the top-level folder are parsed.

Routing Context

Loads the routes from the definition files in the specified directory and makes them available to the underlying Express app. Guarantees that the routes have been registered and are present in the _flit.routing.routes object. Also provides utility functions.

_flit.routing

type:  Object

Holds the variables and functions provided by this unit.

_flit.routing.routes

type:  Object

Set of key-value pairs such that the key is the name of the route definition file parsed from the top-level directory only, and the value is the Object of route definition information from that file.

_flit.routing.redirect(String destination)

returns:  mixed

Returns a handler function that will send a response redirecting the user to the given destination URL. Meant to be used in the route definition files, like so:

// creates a handler function for Flitter that sends a redirect to 'http(s)://app.url/dashboard'
'/dash': [ redirect('/dashboard') ]
global.redirect(String destination)

returns:  mixed

Mapped to _flit.routing.redirect().

libflitter

The Static Unit

libflitter/static/StaticUnit

exports:  class StaticUnit

class StaticUnit

extends:  libflitter/Unit

Tells the underlying Express app to serve static requests from the specified directory and initializes the express-favicon package.

constructor(String static_assets_dir = './app/assets')

overrides:  super.constructor()         returns:  this

Initializes the class. Resolves the fully qualified directory of the static assets (static_assets_dir) and stores it in this.static_asset_directory.

go(express app, function next)

overrides:  super.go()         returns:  undefined

Tells the underlying Express app to serve this.static_asset_directory as static under the base route /assets by calling express.static. Configures the underlying Express install to use the express-favicon package to serve the favicon.ico file in this.static_asset_directory. This establishes the Static context and calls the next function in the stack under it.

provides()

returns:  Object

Returns an Object describing the services, templates, and directories provided by this unit. They are as follows:

Type Key Value Notes
Service Name name "static" The name of the service. This is added to the _flit.services array so other units can check for this unit.
Directory directories.assets this.static_asset_directory The fully-qualified path of the static asset files. This is added to the _flit.directories store so other units can access it.
this.static_asset_directory

type:  String

Set on initialization. Specifies the the fully-qualified path of the static assets. Files in this directory are served by Express statically.

Static Context

This context guarantees that the underlying Express install is configured to serve the files in the specified assets directory with the /assets route prefix.

libflitter

The Error Unit

libflitter/errors/ErrorUnit

exports:  class ErrorUnit

class ErrorUnit

extends:  libflitter/Unit

Creates a catch-all route for HTTP 404 errors and creates a handler for errors in the underlying Express installation.

go(express app, function next)

overrides:  super.go()         returns:  undefined

Creates a catch-all handler that is called if no other route handler is found. This handles HTTP 404 errors by creating a new Error with status=404. Then, creates an Express handler that, if passed any kind of error, returns the view in the views/errors/ directory with the corresponding status code as its name. If the config value server.environment is development, then all errors render the views/errors/development.pug view. All error views are passed the error object as error.

Error Context

This context guarantees that non-existent routes will be handled as 404 errors, and that all errors will return some error view which is passed the error instance.

libflitter

The App Unit

libflitter/app/AppUnit

exports:  class AppUnit

class AppUnit

extends:  libflitter/Unit

The AppUnit starts the Express HTTP server on the configured port and establishes the App context.

go(express app, function next)

overrides:  super.go()         returns:  undefined

Starts the Express HTTP server on the port defined by the config value server.port by calling app.listen(). This establishes the App context and calls the next function in the stack under it.

App Context

This context guarantees that the Express HTTP server has been started.

libflitter

Deployment Store

libflitter/utility/DeployStore

exports:  class DeployStore

class DeployStore

Stores and recalls deploy functions. In Flitter, a deploy function is a one-way, non-reversible process that sets up required files and settings for a module. That may be copying controllers and views to the appropriate directories, seeding the database, whatever. This class is usually instantiated at _flit.deployments.

store

type:  Object

Holds the deployment functions by name.

add(String name, function deploy_function)

returns:  undefined

Adds a deploy_function to this.store with the specified name. This function should accept and handle a callback.

deploy(String name, function callback)

returns:  mixed

Runs the deployment from this.store with the given name and passes it the specified callback. This function returns the value of the deployment function.

libflitter

Directory Store

libflitter/utility/DirectoryStore

exports:  class DirectoryStore

class DirectoryStore

Resolves and stores fully-qualified paths to named directories. This is meant to be used by Flitter units to reference directories programmatically so they can be changed in config. This is usually instantiated in _flit.directories.

store

type:  Object

Stores the registered directories in key-value pairs such that the key is the name of the directory, and the value is the fully-qualified path of the directory.

constructor()

returns:  this

Initializes the class. Sets this.store to a new Object.

add(String name, String directory)

Resolves the specified directory to a fully-qualified path and stores it by name in this.store.

get(String name)

Returns the fully-qualified directory from this.store with the given name.

libflitter

Template Store

libflitter/utility/TemplateStore

exports:  class TemplateStore

class TemplateStore

Stores templates by name and returns their parts on demand. Templates should be instances of Object that specify the template generator, file extension, and destination directory. In Flitter, a template is a type of file that may be created by the developer multiple times with different names. The generator function should accept a class/variable name and should return the text of the new file. Here's an example template Object:

const middleware_template = {
	// The generator function. Should accept (String name) and should return the text of the new file.
	template: require('libflitter/templates/middleware'),
  
  	// The base directory of the template, fully-qualified.
	directory: this.directory,
  
  	// Extension of the new file.
	extension: '.middleware.js'
}
store

type:  Object

Stores the templates in key-value pairs such that the key is the specified name of the template, and the value is the template Object.

constructor()

returns:  this

Initializes the class. Sets this.store to a new Object.

add(String name, Object template)

returns:  undefined

Stores the provided template in this.store by name.

directory(String name)

returns:  String

Returns the directory from the template Object with the given name from this.store.

extension(String name)

returns:  String

Returns the file extension from the template Object with the given name from this.store.

generator(String name)

returns:  function

Returns the template generator function from the template Object with the given name from this.store.

libflitter

libflitter Templates

libflitter/templates/config

exports:  config

config(String name)

type:  function         returns:  String

Function to generate the contents for a new configuration file with name name.


libflitter/templates/controller

exports:  controller

controller(String name)

type:  function         returns:  String

Function to generate the contents for a new controller file with name name.


libflitter/templates/middleware

exports:  middleware

middleware(String name)

type:  function         returns:  String

Function to generate the contents for a new middleware definition file with name name.


libflitter/templates/model

exports:  model

model(String name)

type:  function         returns:  String

Function to generate the contents for a new model schema file with name name.


libflitter/templates/router

exports:  router

router(String name)

type:  function         returns:  String

Function to generate the contents for a new route definitions file with name name.

flitter-forms

flitter-forms

Overview

flitter-forms provides form-template validation and session-based error messages for Flitter. This means that validator schemata are defined for each form, then they are applied to a request submitting information from the corresponding form. That request body is validated using the requirements specified in the form's schema. Any validation errors are written to the session and the user is returned to the form. When the validation completes, the request is passed to the route handler along with the input fields specified in the schema.

Getting Started

We'll take a look at how to create and validate a sample registration form using flitter-forms by defining a validation schema, creating a very basic view, and a handler for it.

Define Validation Schema

Validation schemata contain all fields you wish to access from the form. A validation schema is an Object of key-value pairs where the keys are the names of the fields as they will be submitted and the values are each an Array. Each Array should be a collection of validators that will be applied, in order, to that form.

Let's create a new schema from a template using ./flitter:

./flitter new validator RegisterationForm

This will create the file app/validators/RegistrationForm.validator.js. We will edit it to define the fields in our form and the validator criteria that should be applied to each of those fields:

// RegistrationForm Validator

const RegistrationForm = {
    
  	// field name	validators
    'username': 	[ 'required', 'isAlphanumeric' ],
    'password': 	[ 'required', 'verify', 'isLength:{"min":8}' ]
    
}

module.exports = exports = RegistrationForm

flitter-forms makes available a superset of criteria containing those from the validator package. The name of the criterion should be specified as a String. Any arguments that the criterion takes can be specified as a String of valid JSON separated from the criterion name by a : character. More info on available criteria here.

Create the Form View

Now, we'll create a very basic form in the views/register.pug file:

html
    head
        title Register
    body
        for field in form.errors
            for error in field
                p(style='color: red;') Error: #{error}
        form(action='/register' method='POST' enctype='multipart/form-data')
            input(name='username' placeholder='Username' required)
            br
            input(name='password' placeholder='Password' required)
            br
            input(name='password_verify' placeholder='Verify Password' required)
            br
            button(type='submit') Submit

Note that password_verify is required because we specified the verify criterion for the password field.

Define the Routes

Now, we will add routes for our form to app/routing/routers/index.routes.js:

const index = {

     prefix: '/',
    
    get: {
        '/': [ controller('Home').welcome ],
        '/register': [ controller('Home').show_registration ]
    },
    
    post: {
        '/register': [ controller('Home').handle_registration ]
    },
}

module.exports = index

Here we specified that the GET request should be handled by the show_registration method on the Home controller, and the POST request should be handled by the handle_registration method on the Home controller.

Handle the Requests

We need to add these methods to the Home controller (app/controllers/Home.controller.js). First, add the show_registration method:

show_registration(req, res){
  	// initialize the form in the session
  	const form =_flit.validator('RegistrationForm')
	_flit.forms.initialize(form, req)
	
  	return view(res, 'register', {form: req.session.forms.RegistrationForm})
}

Here, we initialize the form in session. If the form already has data in the session, they will not be destroyed. Then, we render the registration page and pass it the form object as form.

Then, add the handle_registration method (this is just a dummy method):

handle_registration(req, res){
        const validator = _flit.validator('RegistrationForm')
        
        // Tell the validator to handle the request.
        validator.handle(req, res, '/register', (req, res, input) => {
            
            // The validation was successful! Let's just look at the input.
            res.send(JSON.stringify(input))
        })
    }

Forms makes validators available by name through the _flit.validator() function, so we get the RegistrationForm validator. Then, we tell the validator to handle the request. If the validation fails, the user will be redirected to the /register route and the error will be displayed. If it is successful, then the callback function will be executed, responding with the input data as JSON to the user (as a demo).

flitter-forms

The Validation Criteria

In flitter-forms, each field in a form has a number of criteria it must pass to be valid. These criteria are specified by name and may take a number of arguments. Forms uses a superset of the criteria provided by the validator package, so any criteria present there will work with Forms.

Specifying Criteria

Criteria are specified in the validator schema definition. They are specified as an Array of String elements where each element is the name of some criteria. Here's an example:

const TestForm {
  	// field	criteria
  	'age':		[ 'required', 'isInt' ],
}

Here, we tell Forms that the age field is required, and it must be an integer.

Criterion Arguments

If you look at the list of criteria provided by the validator package, you may notice that many of them accept an Object of different arguments. Forms provides a way to specify these arguments. Arguments should be specified as a valid JSON object in the same String as the criterion name. It should be separated from the name by a : character. For example:

const TestForm {
  	// field			criteria
  	'age':				[ 'required', 'isInt:{"min":18}' ],
    'secret_password':	[ 'required', 'equals:its_a_secret' ],
}

Here, we tell Forms that the age field is required, and it should be an integer with a minimum value of 18, inclusive. The latter half of that criterion is a valid JSON object:

{"min":18}

We also tell Forms that the secret_password field is required and it should be equal to the string "its_a_secret". It's important to note that specifying a String as an argument without quotes is valid because it will be parsed into a JSON string. This is equally valid:

equals:"this is a bigger string"

Field Interpolation

Field values can also be interpolated as arguments in other fields. This can be useful when checking equivalence or relating numbers together. To interpolate the value of a field in the argument of another field, simply specify that field's name surrounded on both sides with $ characters, and it will be replaced with the value of the field. Here's an example:

const TestForm {
  	// field			criteria
  	'age':				[ 'required', 'isInt:{"min":13}' ],
    'parent_age':		[ 'required', 'isInt:{"min":$age$}' ],
    'strings_work_too':	[ 'required', 'equals:"$age$ years old"' ],
}

Say someone submits the form, and the age input is 15. Forms would stipulate that the age is required and must be greater than 13, the parent age is required and must be greater than 15, and the strings_work_too field is required and must be equal to "15 years old".

Available Criteria

flitter-forms ⊃ validator

The criteria in flitter-forms are a superset of those in the validator package. You can use any of the criteria listed here, with their arguments.

criteria ∉ validator

Forms also provides a few other criteria that are not a part of the validator package. Here they are:

Criterion Name Description
required The associated field must be present in the input, and must not be equal to an empty String.
verify

The associated field must have a corresponding field with the same name suffixed with _verify. The two fields must be equal.

 

For example, if the password field has the verify criterion, then password_verify must be present, and it must be equal to password.

flitter-forms

The Forms Unit

flitter-forms/FormsUnit

exports:  class FormsUnit

class FormsUnit

extends:  libflitter/Unit

directory

type:  String

Stores the fully-qualified path to the directory that holds the validator definitions. Each of the files in this directory should export an Object of key-value pairs such that each of the keys is the name of an input field, and each of the values is an Array of validator check names.

constructor(String directory = './app/validators')

overrides:  super.constructor()         returns:  this

Initializes the class. Resolves the directory into the fully-qualified path of the folder that holds the validator definitions and stores it in this.directory.

go(express app, function next)

overrides:  super.go()         returns:  undefined

Initializes flitter-forms. Registers this.deploy() as the forms deployment script. Creates a new Object in _flit.forms. Then, iterates over files in this.directory recursively and imports the validator definitions. For each definition, it creates a new instance of flitter-forms/Validator using the definition schema. Each of these instances is assigned a name which, according to Flitter convention, is parsed from the file name and follows the pattern for sub-directory naming. For example, the Validator created from the file auth/Login.validator.js would be named auth:Login. Each of these instances is stored in _flit.forms.validators by name. Then, an access function is created, as well as a function to initialize a new form in the session. This establishes the Forms context and calls the next function in the stack under it.

provides()

overrides:  super.provides()         returns:  Object

Returns an Object describing the services, templates, and directories provided by this unit. They are as follows:

Type Key Value Notes
Service Name name "flitter-forms" The name of the service. This is added to the _flit.services array so other units can check for this unit.
Directory directories.validators this.directory The fully-qualified path of the validator definition files. This is added to the _flit.directories store so other units can access it.
Template templates.validator Object Template that creates new config file in this.directory.
deploy(function callback)

returns:  undefined

Deploys the default ./validators directory with a sample validator definition file. Once this has completed, it calls the callback function.

Forms Context

Guarantees that the form validator schemata have been loaded and are available in _flit.forms.validators. Also makes available other utility functions.

_flit.forms

type:  Object

Holds variables and functions provided by this unit.

_flit.forms.validators

type:  Object

Contains the instances of flitter-forms/Validator created from the imported validation schemata such that the keys are the names of the schemata. Following Flitter convention, the names are parsed from the files and follow the convention for sub-directory naming. That is, sub-directories can be specified with : characters. For example, auth:LoginForm would reference app/validators/auth/LoginForm.validator.js.

_flit.forms.initialize(flitter-forms/Validator validator, express.request request)

returns:  undefined

Initializes the corresponding form of the provided validator in request.session. This provides access to the error messages.

_flit.validator(String name)

returns:  flitter-forms/Validator

Returns the validator in _flit.forms.validators with the corresponding name. Validator names are parsed from the schema files and follow the Flitter convention for sub-directory naming. That is, sub-directories can be specified with : characters. For example, auth:LoginForm would reference app/validators/auth/LoginForm.validator.js.

flitter-forms

The Validator Class

flitter-forms/Validator

exports:  class Validator

class Validator

Handles validation of input and parsing of schema criterion, as well as validation and sanitation of input.

name

type:  String

The name of this Validator. Should not contain spaces.

schema

type:  Object

Collection of key-value pairs specifying the fields this Validator covers, and their input requirements. Each key should be the name of an input field as it would appear in the request body. Each value should be an Array of validator criterion and options to be applied to the field.

constructor(String name, Object schema)

returns:  this

Initializes the class. Stores the name in this.name and the schema in this.schema.

validate(Object input, function callback = false)

returns:  Error|Boolean

Iterates over the this.schema fields and applies each field's validation criteria to the corresponding input field. If any of these criteria fail, their error messages are collected. If the validation succeeds, then the input fields are masked, which is to say that all input fields NOT in this.schema are removed. Then, the callback is called and passed a Boolean value which is true if the validation fails. If the validation has failed, an Object of key-value pairs where the keys are the name of the field, and the values are each an Array of error messages is passed to the callback as well. If the validation succeeds, then the Boolean value is false and the callback is passed the masked input. If at any point this validation encounters a programmatic error, this function will return an instance of Error which should be handled accordingly.

If no callback is provided, this function will return a Boolean value that is true if the validation has failed. However, this is not the recommended implementation.

mask_returns(Object schema, Object input)

returns:  Object

Masks the input such that any fields not present in the schema are removed.

make_error_chain()

returns:  Object

Creates a template to store error messages by field. That is, an Object of key-value pairs where the keys are the names of the fields in this.schema and the values are each an empty Array to hold error messages.

extract_validator_arguments(String criterion, Object input)

returns:  Object

Field criteria can be specified with one or more arguments. A criterion argument can be specified after the : character in the criterion String. The argument should be valid JSON. Here's an example:

const sample_form_definitions = {
  	// field name		criteria
  	age				:	[ 'isInt:{"min":13}', 'required' ],
  	parent_age		:	[ 'isInt:{"min":$age$}' ],
  	secret_password :   [ 'equals:thisisapassword' ],
}

Here, the age field is specified to be required, and must be an integer greater than 13. Note that the argument on isInt is valid JSON. The parent_age field must be an integer greater than the value of the age field. Field interpolation can be specified by surrounding the name of the field with $ characters. The secret_password field must be equal to 'thisisapassword'. Unformatted strings are each parsed as a String.

parse_interpolators(String string)

returns:  Array

Given a string, it finds names of fields to be interpolated (that is, the name of a field surrounded by $ characters) and returns an Array of the field names.

interpolate_fields(String string, Object inputs)

returns:  String

Given a string that may contain $-surrounded field names to be interpolated, this function replaces them with the corresponding values from the specified input, then it returns the string.

stringify_argument(String argument = '')

returns:  String

Turns a criterion argument string into human readable format. This is specifically useful for arguments which specify a min and/or max attributes. This is usually passed to the message generator to make error messages more explicit.

For example:

'{"min":8, "max":10}'    becomes    'greater than 8 and less than 10'

handle(express.request req, express.response res, String error_route, function callback)

Handles a request by calling this.validate on the req.body. If the validation fails, the error Object is written to req.session.forms[this.name].errors, and the user is redirected to error_route. If the validation succeeds, the callback function is called and is passed the req, res, and input.

flitter-forms

The Message Generator

flitter-forms/Messages

exports:  function

function(String criterion, String field, String requested_value = '')

returns:  String|Error

Using the specified criterion name, look up the human readable error message filling in the field name and, if relevant, the requested_value of the field and return the message.

flitter-forms

The flitter-forms Templates

flitter-forms/templates/validator

exports:  validator

validator(String name)

returns:  String

Template generator function to get the contents of a new validator definition with name name.

flitter-auth

flitter-auth

Overview

flitter-auth provides a basic user-authentication framework for Flitter. When deployed, it creates controllers, middleware, routes, and views to add login/registration/session/route-protection functionality to your application. The sample views that ship with flitter-auth provide a login page, registration page, very basic dashboard, and error page.

flitter-auth creates a basic User model that stores the username, UUID, hashed password, and a misc. JSON data array for each user. When a user logs in, this object (sans password) is stored in the session so it can be used when handling requests. When a user logs out, this object is removed. Passwords are encrypted using bcrypt, and their hashes are stored.

Quick Start

By default, Flitter ships with flitter-auth available. To set it up, just run the deployment:

./flitter deploy auth

Now, you should be able to access the /auth/register, /auth/login, and /auth/dash routes. flitter-auth makes its files available in the app-space so they can be customized and extended to work with your app better.

flitter-auth

The Auth Unit

flitter-auth/AuthUnit

exports:  class AuthUnit

class AuthUnit

extends:  libflitter/Unit

go(express app, function next)

returns:  undefined

Registers this.deploy() as the auth deployment. This establishes the FlitterAuth context and runs the next function in the stack under it.

provides()

returns:  Object

Returns an Object describing the services, templates, and directories provided by this unit. They are as follows:

Type Key Value Notes
Service Name name "flitter-auth" The name of the service. This is added to the _flit.services array so other units can check for this unit.
deploy(function callback)

Deploys the files necessary to use flitter-auth. This includes deploying controllers, models, middleware, routers, and views. Then, after this has completed, it calls the callback function.

FlitterAuth Context

Guarantees that the auth deployment has been registered.

flitter-auth

The Auth Controller

flitter-auth/deploy/controllers/Auth.controller.js

deployed to: app/controllers/auth/Auth.controller.js         exports:  class Auth

class Auth

create_auth_session(express.request req, flitter-auth/deploy/models/User user)

returns:  undefined

Creates a new Object in req.session.auth that indicates that the user is logged in and provides information about them, but sanitizes the password field. This Object contains the following:

Key Value
authenticated true
uuid user.uuid
user user (password field is blanked)
destroy_auth_session(express.request req)

returns:  undefined

Removes any information in req.session.auth by reassigning it to an empty Object. This is effectually "logging out" the user, but it will succeed even if no user existed in the first place.

register_get(express.request req, express.response res)

returns:  mixed

Serve the register view. Looks for session data from any submitted auth forms. If session data exists, retrieve it and delete it. Pass that data to the auth/register view and serve said view. Returns the value of view().

register_post(express.request req, express.response res)

returns:  mixed

Handles an attempt at registration. Validates the required input from req.body and formatting for the username, password, and password_verify fields. If any of these fail validation, redirect back to /auth/register after writing the errors and username to the session. If validation succeeds, it creates a new instance of flitter-auth/deploy/models/User with the provided input as data. After saving the user, it returns the auth/register_success view.

logout(express.request req, express.response res)

returns:  mixed

Calls this.destroy_auth_session on the provided req then returns the auth/logged_out view.

login_get(express.request req, express.response res)

returns:  mixed

Serve the login view. Looks for session data from any submitted auth forms. If session data exists, retrieve it and delete it. Pass that data to the auth/login view and serve said view. Returns the value of view().

login_post(express.request req, express.response res)

returns:  mixed

Handles an attempted login. Validates the required input from req.body and formatting for the username and password fields. Then, it attempts to retrieve the flitter-auth/deploy/models/User instance with the corresponding username and check the password against the stored hash. If any of this fails, it writes the errors and username to session data and redirects back to the /auth/login route.

If the authentication succeeds, it checks to see if req.session.destination is set. If it is, it redirects the user there. If not, it redirects the user to the /auth/dash route.

dash_get(express.request req, express.response res)

returns:  mixed

Returns the auth/dash view. Passes req.session.auth.user to the view as user.

flitter-auth

The User Model

flitter-auth/deploy/models/User.model.js

deploys to:  app/models/auth/User.model.js         exports: User

const User

type:  Object

username

type:  String

Stores the username of the User. Validated to be alphanumeric by the Auth controller.

password

type:  String

Stores the B-Crypt password hash of the User.

data

type:  String

Stringified JSON object with misc data.

uuid

type:  String

Universally-Unique ID of the user. Usually set by the uuid package in the Auth controller.

flitter-auth

The RequireAuth Middleware

flitter-auth/deploy/routing/middleware/RequireAuth.middleware.js

deploys to:  app/routing/middleware/auth/RequireAuth.middleware.js         exports:  RequireAuth

class RequireAuth

Middleware that, when applied, redirects the user to the login page if they are not authenticated.

test(express.request req, express.response res, function next)

returns:  undefined

Checks if req.session.auth.user exists, or req.session.auth.authenticated is true. If these are, that means that a user is logged in. If this check passes, then call the next function in the stack, allowing the request to continue. Otherwise, write req.originalUrl to req.session.destination and redirect to the /auth/login route.

flitter-auth

The RequireGuest Middleware

flitter-auth/deploy/routing/middleware/RequireGuest.middleware.js

deploys to:  app/routing/middleware/auth/RequireGuest.middleware.js         exports:  RequireGuest

class RequireGuest

Middleware that, when applied, redirects the user to the login page if they are not authenticated.

test(express.request req, express.response res, function next)

returns:  undefined

Checks if req.session.auth.user exists, or req.session.auth.authenticated is true. If these are, that means that a user is logged in. If this check passes, then the errors/requires_guest view is rendered. Otherwise, the user is a guest (non-authenticated session), and the next function in the stack is called, allowing the request to continue.

flitter-auth

The Auth Routes

flitter-auth/deploy/routing/routers/auth.routes.js

deploys to:  app/routing/routers/auth.routes.js         exports:  index

const index

Route definitions for flitter-auth.

prefix

type:  String

The prefix for all routes defined herein. Set to /auth .

get

type:  Object

Collection of routes that use the GET method, and their handlers. As follows:

Route Notes
/register Display a registration page. By default, it applies the auth:RequireGuest middleware and hands off to the register_get() function on the Auth controller.
/login Display a login page. By default, it applies the auth:RequireGuest middleware and hands off to the login_get() function on the Auth controller.
/logout Handle a logout. By default, it applies the auth:RequireAuth middleware and hands off to the logout() function on the Auth controller.
/dash Display the dashboard. By default, it applies the auth:RequireAuth middleware and hands off to the dash_get() function on the Auth controller. This will probably be changed to a redirect to your app's particular dashboard.
post

type:  Object

Collection of routes that use the POST method, and their handlers. As follows:

Route Notes
/register Handle a registration submission. By default, it applies the auth:RequireGuest middleware and hands off to the register_post() function on the Auth controller.
/login Handle a login submission. By default, it applies the auth:RequireGuest middleware and hands off to the login_post() function on the Auth controller.

flitter-cli

flitter-cli

Overview

flitter-cli provides a number of CLI tools for Flitter with the goal of making the Flitter development flow easier. In Flitter, this functionality is made available in the form of the ./flitter command.

This command exposes an endpoint for the flitter-cli logic, and is usually formatted like so:

#!/usr/bin/env node
/*
 * ./flitter
 * -------------------------------------------------------------
 * The ./flitter command is used to interact with Flitter and its tools
 * in the development environment. Currently, it lends access to Flitter
 * shell, which is like a Node interactive prompt, but it's launched from
 * within the same context as the Flitter HTTP server, allowing developers
 * to interact with Flitter directly.
 */
const CliUnit = require('flitter-cli/CliUnit')
const cli = new CliUnit()

/*
 * Since the ./flitter command loads a full, running instance of
 * Flitter, it also uses the Flitter units file. The only difference
 * is that the CliUnit replaces the final AppUnit with InteractiveUnit,
 * which launches the interactive prompt.
 */
const units = require('./Units.flitter')

/*
 * process the command line arguments and interpret the given
 * directives.
 */
cli.interpreter(units)

This expects the file to be called with command line inputs, which flitter-cli processes then handles.

flitter-cli

The CLI Unit

flitter-cli/CliUnit

exports:  class CliUnit

class CliUnit

extends:  libflitter/Unit

Processes the specified CLI arguments and executes them.

go(express app, function next)

overrides:  super.go()         returns:  undefined

Calls the next function in the stack. This is here for future use.

serve(Object units)

returns:  undefined

Launches Flitter with an instance of libflitter/app/FlitterApp as the final Unit. This is effectively a "normal" Flitter server. It launches Flitter using the Unit name-Unit instance key-value pairs passed in the units Object.

interactive(Object units)

returns:  undefined

Launches Flitter with an instance of flitter-cli/InteractiveUnit as the final Unit. Rather than starting the normal Flitter HTTP server, this launches an interactive shell. It launches Flitter using the Unit name-Unit instance key-value pairs passed in the units Object.

directive(Object units)

returns:  undefined

Launches Flitter with an instance of flitter-cli/DirectiveUnit as the final Unit. Rather than starting the normal Flitter HTTP server, this executes an arbitrary function from within all Flitter contexts. It launches Flitter using the Unit name-Unit instance key-value pairs passed in the units Object.

interpreter(Object units)

returns:  undefined

Parses CLI directives and handles them appropriately.

provides()

returns:  Object

Returns an Object describing the services, templates, and directories provided by this unit. They are as follows:

Type Key Value Notes
Service Name name "flitter-cli" The name of the service. This is added to the _flit.services array so other units can check for this unit.

 

flitter-cli

The Directive Unit

flitter-cli/DirectiveUnit

exports:  class DirectiveUnit

class DirectiveUnit

Executes an arbitrary function. This is designed to replace the FlitterApp unit.

func

type:  function

This is the function that will be executed when this unit is initialized.

constructor(function func)

returns:  this

Initializes the class. Stores the specified func in this.func.

go(express app, function next)

returns:  undefined

Executes this.func and passes an exit function so it can end the process. Does NOT call the next function in the stack. This ends Flitter's execution stack.

flitter-cli

The Interactive Unit

flitter-cli/InteractiveUnit

exports:  class InteractiveUnit

class InteractiveUnit

Starts a Nesh interactive shell as the last Flitter unit. This is designed to replace the FlitterApp unit.

go(express app, function next)

Launches a Nesh shell from within all of Flitter's contexts. Calls process.exit when the shell ends. Does NOT call the next function in the stack.

flitter-less

flitter-less

Overview

flitter-less is an in-place compiler for LESS style-sheets for Flitter. That is, LESS styles placed in app/assets/less/ can be accessed as compiled CSS directly by referencing the name of the style-sheet after the /style-asset route.

Installation

Install the flitter-less package:

yarn install flitter-less

Add the Unit to the Units.flitter.js file, in the "Custom Units" section:

const FlitterUnits = {

    // ... other units ...
    
    
    /*
     * Custom Flitter Units
     * -------------------------------------------------------------
     * Custom units should be specified here. They will be loaded in order
     * after the core of Flitter has been initialized.
     */
  	'Less'		: new (require('flitter-less/LessUnit'))(),
	
  	// ... other units ...
}

module.exports = exports = FlitterUnits

Use

Using flitter-less is pretty straightforward. Place your .less files in app/assets/less/. Now, you can access them at the /style-asset route. For example, app/assets/less/index.less can be accessed at /style-asset/index.css.

flitter-less

The Less Unit

flitter-less/LessUnit

exports:  class LessUnit

class LessUnit

extends:  libflitter/Unit

A wrapper for the express-less package for Flitter. Makes .less assets available on the /style-asset route.

directory

type:  String

Contains the fully-qualified path to the directory with the .less files.

constructor(String style_path = './app/assets/less')

overrides:  super.constructor()  returns:  this

Initializes the class. Resolves the fully-qualified path to the directory with the .less files and stores it in this.directory.

go(express app, function next)

overrides:  super.go()  returns:  undefined

Initializes the unit. Registers the express-less package with the underlying Express app and points it to this.directory. Compiled .less files are served at the route /style-asset. This establishes the Less context and executes the next function in the stack under it.

Less Context

Guarantees that the less files in the specified directory are available at /style-asset. Note that it this context need not be above the controllers, as long as it is established above the final App context.

flitter-upload

flitter-upload

Overview

flitter-upload provides a simple, middleware-based file upload system for Flitter. This allows files to be uploaded, stored, and categorized seamlessly. This is done with the hope of reducing the hassle of keeping track of and serving uploaded files. Here, we'll build a sample picture uploader to take a look at how to use flitter-upload.

Step 0: Create the basic upload form and routes.

Create views/upload.pug:

html
    body
        form(method='post' enctype='multipart/form-data')
            input(type='file' name='img' required)
            button(type='submit') Upload

Create the route in routing/routers/index.routes.js:

get: {
    '/': [ controller('Home').welcome ],
    '/upload': [ controller('Home').get_upload ],
},

Create the controller function in controllers/Home.controller.js:

get_upload(req, res){
    return view(res, 'upload')
}

Step 1: Create the upload submission route.

Create the route in routing/routers/index.routes.js:

post: {
    '/upload': [ _flit.upload('img', 'upload-img'), controller('Home').post_upload ]
},

Here, we invoke the _flit.upload() middleware before we pass off control to the post_upload() handler. We call this middleware with two arguments. The first is the name of the file uploaded. In this case, img. It should match exactly the name attribute of the file input field. The second is an arbitrary string that categorizes the uploaded files. In our case, they will be tagged upload-img. The files are then stored in the specified upload directory, uploads/ by default.

Step 2: Add the controller method.

Add the following to controllers/Home.controller.js:

post_upload(req, res){
    const file_name = req.files.img.new_name
    
    res.send('File uploaded as: '+file_name)
}

The _flit.upload() middleware replaces whatever file name it uploads with an instance of flitter-upload/deploy/File in req.files. Here, that instance is created in req.files.img. Then, we just send a text response to the user with the UUID name of the file.

Step 3: Add the retrieval route.

Create the route in routing/routers/index.routes.js:

get: {
    '/': [ controller('Home').welcome ],
    '/upload': [ controller('Home').get_upload ],
    '/file/:name' : [ controller('Home').get_file ],
},

Add the handler function in controllers/Home.controller.js:

get_file(req, res, next){
  	// Get the name of the file from the route.
    const file_name = req.params.name
    
    // Find the file instance with the corresponding name.
    _flit.uploads.model.findOne({new_name: file_name}, (error, file) => {
      	// If an error occurs, let Flitter handle it.
        if ( error ) next(error)
        
      	// If the file isn't found, throw a 404 error.
      	if ( file === null ) {
            const e = new Error('File with name not found.')
            e.status = 404.
            next(e)
        }
      	else {
          	// Otherwise, serve the file.
        	_flit.uploads.serve(res, file)
        }
    })
}

Now the file with the given name will be served to the user. Here, we find the instance of the flitter-upload/deploy/File model with the given file name. If the file is found, serve it to the user. flitter-upload handles the MIME type setting.

flitter-upload

The Upload Unit

flitter-upload/UploadUnit

exports:  class UploadUnit

class UploadUnit

extends:  libflitter/Unit

Creates a middleware function that uploads files based on the field name.

directory

type:  String

The fully-qualified path to the folder where files should be uploaded.

constructor(String upload_directory = './uploads')

returns:  this

Initializes the class. Resolves the upload_directory and stores it in this.directory.

go(express app, function next)

returns:  undefined

Loads the unit. Creates  the _flit.uploads object and helper functions. This establishes the Upload context and calls the next function in the stack under it.

upload(String file)

returns:  function

Returns an Express middleware function that uploads the file provided in req.files[file] to this.directory and creates an instance of flitter-upload/deploy/File in req.files[file], then calls the next function in the stack. If a uploaded file by that name doesn't exist, the middleware does nothing and calls the next function.

serve(express.response res, flitter-upload/deploy/File file)

returns:  undefined

Serves the file as the response to the user.

delete(flitter-upload/deploy/File file, function callback)

returns:  undefined

Deletes the specified file from the disk and removes its record. Then the callback is executed. If an error occurs, it is passed to the callback.

deploy(function callback)

returns:  undefined

Runs the deployment for this unit. Copies the flitter-upload/deploy/File.model.js file to models/upload/File.model.js, then the callback is executed. If an error occurs, it is passed to the callback.

Upload Context

_flit.uploads

type:  Object

Holds the variables and functions provided by this unit.

_flit.uploads.upload(String file)

type:  function

Mapped to this.upload(). Should be called as middleware on the route of the file upload submission.

_flit.uploads.serve(express.response res, flitter-upload/deploy/File file)

type:  function

Mapped to this.serve(). Can be called from within a controller to serve an uploaded file.

_flit.uploads.destination

type:  String

Fully-qualified path to the folder where uploaded files are stored.

_flit.uploads.model

type:  mongoose/Model

The reference to the flitter-upload/deploy/File model. Used to access and store new file records.

_flit.uploads.remove(flitter-upload/deploy/File file, function callback)

type:  function

Mapped to this.delete(). Removes the file from disk and deletes its record.

_flit.upload(String file)

type:  function

Mapped to _flit.uploads.upload.

deployment:  upload

Moves flitter-upload/deploy/File.model.js to models/upload/File.model.js.

flitter-upload

The File Model

flitter-upload/deploy/File

exports:  File

const File

type:  Object

Schema for the File database record. Stores the original file name, new UUID file name, MIME type, and a customizable type name.

Name Type Notes
original_name String The original name of the file, as it was when uploaded. For example, "image001.jpg".

new_name

String The new name of the file. This is usually set to a UUID generated by flitter-upload when a file is stored.
mime String The MIME type of the file.
type String An arbitrary string categorizing the file. This is designed to be used by the developer to designate different types of uploads. For example, "profile-photo" or "user-pdf".

flitter-agenda

flitter-agenda

Overview

flitter-agenda provides a wrapper for the Agenda scheduler. This allows jobs to be defined and scheduled programmatically, and persistently. It uses the same MongoDB database as the rest of the Flitter application.

For the most part, using flitter-agenda is identical to using vanilla Agenda. The only main difference is that jobs should be defined in individual files in a given directory (by default, app/jobs). Each of these files should export a class that extends flitter-agenda/Job. The name of a job is parsed from its file name, recursively. flitter-agenda uses the Flitter convention for sub-directory naming, meaning that sub-directories are delineated with a : character. For example, the job named auth:CleanUsers would be located in the file app/jobs/auth/CleanUsers.job.js.

Installation

flitter-agenda doesn't ship with Flitter by default, but it's pretty easy to add. First, install the package:

yarn add flitter-agenda

Now, modify the Units.flitter.js file. Add the add the following line to the "Custom Flitter Units" section to tell Flitter to load flitter-agenda:

'Agenda'		: new (require('flitter-agenda/AgendaUnit'))(),

Et voilà! You should now have access to flitter-agenda.

Creating a Sample Job

As an example, we'll create a sample job that prints a some text to the console.

First, create a new job definition file. We can do this using the built in template with the ./flitter command:

./flitter new job Log

This will create the file app/jobs/Log.job.js. Open it up, and define the actual logic for the job:

const Job = require('flitter-agenda/Job')

class LogJob extends Job {

    exec(job, done){
        
        const {msg} = job.attrs.data
        log(msg)
        
        done()
    }
}

module.exports = exports = LogJob

Here, we take the job argument msg and pass it to Flitter's log() function.

We can test out our job by running it from the Flitter shell. Here's a sample output:

sh-4.4$ ./flitter shell
powered by Flitter, © 2019 Garrett Mills
(flitter)> _flit.sched.scheduler.schedule('in 3 seconds', 'Log', {msg: "This is a message!"})
Promise { <pending> }
(flitter)> This is a message!

_flit.sched.scheduler is the running instance of Agenda, so you can call any function on it like you normally would such as schedule(), every(), even define(). (See the Agenda docs.) Here, we scheduled the Log job we created to run 3 seconds after the schedule() function is called, and passed it a message.

flitter-agenda

The Agenda Unit

flitter-agenda/AgendaUnit

exports:  AgendaUnit

class AgendaUnit

extends:  libflitter/Unit

Initializes the Agenda scheduler for Flitter. Loads job definitions from the specified directory of classes which extend flitter-agenda/Job and registers them with the scheduler. Starts the scheduler. This establishes the Agenda context and calls the next function under it.

directory

type:  String

Fully-qualified path to the folder where the job definition class files are stored.

constructor(String directory = './app/jobs')

returns:  this

Initializes the class. Resolves the provided directory and stores it in this.directory.

go(express app, function next)

returns:  undefined

Initializes the unit. Creates a new Agenda scheduler and connects it to the MongoDB server. Creates the _flit.sched object, then calls this.load_jobs() and passes it the scheduler and the next function in the stack.

load_jobs(agenda sched, function next)

returns:  undefined

Loads the job definition classes from the files in this.directory that end with the .job.js extension, recursively. Each job is assigned a name which is parsed from the file name and sub-directory. flitter-agenda follows the Flitter convention for sub-directory naming -- sub-directories are delineated with a : character. For example, the file app/jobs/auth/CleanUsers.job.js would be named auth:CleanUsers. These job classes are initialized and registered with the scheduler. Finally, this.daemon() is called and passed the next function.

daemon(function next)

returns:  undefined

Starts the scheduler. Once that has completed, it calls the next function from within that context.

provides()

returns:  Object

Returns an Object describing the services, templates, and directories provided by this unit. They are as follows:

Type Key Value Notes
Service Name name "flitter-agenda" The name of the service. This is added to the _flit.services array so other units can check for this unit.
Directory directories.jobs this.directory The fully-qualified path of the job definition files. This is added to the _flit.directories store so other units can access it.
Template templates.job Object Template that creates new job definition file in this.directory.

Agenda Context

Guarantees that the job definitions have been loaded and registered with the scheduler and that the scheduler has been started.

_flit.sched

type:  Object

Holds the variables and functions provided by this unit.

_flit.sched.scheduler

type:  agenda

Instance of the Agenda scheduler. The scheduler has already been started via the start() method. This can be used to schedule and manage jobs as per the Agenda docs.

_flit.sched.jobs

type:  Object

Set of key-value pairs such that the value is an instance of flitter-agenda/Job, and the value is the name of the job. Job names are parsed from file names and follow the Flitter convention for sub-directory naming. That is, sub-directories should be delineated with a : character. For example, the job defined in the file app/jobs/auth/CleanUsers.job.js would be named auth:CleanUsers.

flitter-agenda

The Job Definition Class

flitter-agenda/Job

exports:  Job

class Job

Defines a job that is registered with the Agenda scheduler when flitter-agenda is loaded.

exec(agenda.job job, function done)

returns:  undefined

Does the job. This is executed by the scheduler when the job is run.