Tutorial: Getting Started 2: Routing

Getting Started 2: 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: [
        _flitter.mw('HomeLogger'),
    ],
    get: {
        '/': [ _flitter.controller('Home').welcome ],
    },
    post: {

    },
}

module.exports = 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 _flitter.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 _flitter.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 _flitter.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 _flitter.mw() function like so: _flitter.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 = [
    _flitter.mw('RouteLogger'),
    _flitter.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: [
        _flitter.mw('HomeLogger'),
    ],
    get: {
        '/': [ _flitter.controller('Home').welcome ],
    },
}

module.exports = 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: {
        '/': [ _flitter.mw('HomeLogger'), _flitter.controller('Home').welcome ],
    },
}

module.exports = 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 _flitter.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

Next: 3: Views & Static Assets