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: [
// 'MiddlewareName',
],
get: {
// e.g. 'middleware::MiddlewareName'
// e.g. 'controller::ControllerName.method'
'/': [ '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
.
- e.g. if you define
middleware
is an array of middleware to be applied, in order, to all routes in the file.- You should refer to middleware by their canonical name, unprefixed. That is, if you want to apply the
app/routing/middleware/util/Config.middleware.js
middleware, you would refer to it asutil:Config
.
- You should refer to middleware by their canonical name, unprefixed. That is, if you want to apply the
get
defines routes for theGET
method, andpost
for thePOST
method- These should be objects where the key is the route's URI, and the value is an array of canonical names referring to handlers to be applied, in order, when the route is handled. Route-specific middleware should be included here.
- Because this array can contain both controller methods and middleware handlers, you must refer to these items by their fully-qualified canonical names. That is, the name with the type prefix. So, if you wanted to apply the same
app/routing/middleware/util/Config.middleware.js
file to a specific route, you would call itmiddleware::util:Config
. - Likewise, every route should contain at least one controller method to send the response. Say you wanted to call the
welcome
method on theapp/controllers/Home.controller.js
file, you would refer to it ascontroller::Home.welcome
. - Supported HTTP verb groups are
get
,post
,put
,delete
,copy
,patch
.
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.
Recall that route specific information is not available in the route group level. That is, a middleware applied to an entire file cannot access route specific information like request.params
. However, it can access this information from the route-level handlers.
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
.
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 from flitter-auth
(Flitter's first-party auth provider):
/*
* UserOnly Middleware
* -------------------------------------------------------------
* Allows the request to proceed if there's an authenticated user
* in the session. Otherwise, redirects the user to the login page
* of the default provider.
*/
const Middleware = require('libflitter/middleware/Middleware')
class UserOnly extends Middleware {
async test(req, res, next, args = {}){
if ( req.is_auth ) return next()
else {
// If not signed in, save the target url so we can redirect back here after auth
req.session.auth.flow = req.originalUrl
return res.redirect('/auth/login')
}
}
}
module.exports = UserOnly
Here, the middleware checks if the request.is_auth
flag has been set. This is set by flitter-auth
's global middleware when a valid user has been authenticated. If this flag is set, then we allow the request to continue. Otherwise, we redirect to the login page. The next()
call is very important because it allows the request to continue being processed by the defined handlers for that route.
Middleware must end in one of two ways. It either sends a response itself, or it calls next()
and allows the next handler to deal with the request. Failing to do one of these things will result in the user's browser hanging while it waits for a response.
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 referenced using its canonical name. All three of the places middleware is applied in Flitter accept references in this format. The reason for this is that Flitter applies processing and dependency injection to the middleware, so it should always be instantiated by the framework, not the user.
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 = [
'util:RouteLogger',
'auth:Utility',
'my:custom:MiddlewareName',
]
module.exports = exports = Middleware
Here, the util:RouteLogger
, auth:Utility
, and my:custom:MiddlewareName
middlewares are applied. This is done, in order, for every request that is handled by Flitter. Note that, because only middleware is defined in this file, you should use the unqualified canonical name (i.e. the name without the middleware::
prefix).
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 in that file. For example:
// app/routing/routers/index.routes.js
const index = {
prefix: '/',
middleware: [
'util:HomeLogger',
],
get: {
'/': [ '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. So, navigating to the /
route, the request would pass through global middleware first, then util:HomeLogger
, then the route specific handler.
Route Middleware
Finally, middleware can be applied to individual routes by adding the middleware to the array of canonical names in a given routes file. For example:
// app/routing/routers/index.routes.js
const index = {
prefix: '/',
middleware: [],
get: {
'/': [
'middleware::util:HomeLogger',
'controller::Home.welcome',
],
},
}
module.exports = exports = index
Here, the util:HomeLogger
middleware will be applied first, then the Home.welcome
controller method will be called. Because you can specify either middleware or controller handlers in this array, you must include the qualified canonical name (i.e. the name with the middleware::
or controller::
prefix).
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 canonical name within the framework. 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
:
const Middleware = require('libflitter/middleware/Middleware')
/*
* file_name Middleware
* -------------------------------------------------------------
* Put some description here!
*/
class file_name extends Middleware {
/*
* 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, args = {}){
// Do stuff here
/*
* Call the next function in the stack.
*/
next()
}
}
module.exports = exports = file_name
Next: Views & Static Assets