Rock-solid structured application layout for building APIs and web apps in Node.js.
The goal of slay
is to provide the absolute minimum amount of consistency in a Node.js
application without forcing an enormous amount of convention onto users. The consistency
goal also centers around encouraging modularity in application-level code for maximum reuse.
This is accomplished through three simple features: application layout, "preboots", and a consistent application startup.
By convention slay
looks for user-defined modules in the following locations:
lib/preboots
lib/middlewares
lib/routes
This is done using standard, built-in Node.js module loading which means that each of these could be individual files of the same name instead of folders. Feel free to mix and match as your application's complexity grows. e.g.:
lib/preboots/index.js
lib/middlewares.js
lib/routes/index.js
"A preboot is a middleware for application extensibility"
That is, instead of function (req, res, next)
a preboot is function (app, options, done)
.
By enforcing a consistent function signature to application extensibility all require ordering
problems become trivial. For example:
lib/preboots/index.js
module.exports = function (app, options, done) {
//
// **SCHEDULE** the attachment and initialization of
// connections to our models.
//
app.preboot(require('./models'));
done();
};
lib/preboots/models.js
module.exports = function (app, options, next) {
//
// Attach all of the models for our API / microservices
// to the app itself and connect to them
// (e.g. initialize TCP sockets, etc).
//
app.models = require('../models');
app.models.connect(next);
};
While this may seem too obvious it does several things:
- Makes application extensibility simple and elegant. No complex plugin system required.
- Ensures that all application extensibility is ordered in a single location:
lib/preboots/index.js
. - Encourages modularity through extending the core app by creating new preboots instead of creating an arbitrary, ad-hoc set of utility modules with a (potentially) much more complex internal dependency graph.
const slay = require('slay');
const app = new slay.App(__dirname);
app.start(options, function (err) {
if (err) { throw err; }
app.log.info(`Listening on ${app.config.get('http')`);
});
Calling app.start
above will trigger two main interceptors:
"setup"
interceptor.
/preboots
will be loaded inapp.before('setup')
routers
app.perform('routers')
triggered inapp.before('setup')
app.router
will be available byapp.after('routers')
or byapp.perform('setup')
"start"
interceptor
lib/routes
will be loaded inapp.before('start')
lib/routes/index.js
should callapp.perform('actions')
once to make sure all routes fromapp.router
are loaded in the app.
lib/middlewares
will be loaded inbefore('start')
For more information look at
App.Bootstrap
The App
exposed by slay
has all of the functionality exposed by an app
created by express along with:
Method | Description | Inherited from |
---|---|---|
App.bootstrap |
Core slay bootstrap flow |
slay.App |
app.hookable |
Defines a hookable action | slay.App |
app.stack |
Defines a middleware stack | slay.App |
app.config |
Config loading through nconf |
config preboot |
app.log |
Logger defined through winston |
logger preboot |
app.routes |
Top-level express Router |
routers preboot |
app.preboot |
Schedule a preboot | broadway |
app.mixin |
Add functionality into the app | broadway |
app.start |
Start the application | broadway |
app.close |
Shutdown the application | broadway |
app.perform |
Execute a named interceptor | understudy |
app.before |
Execute before a named interceptor | understudy |
app.after |
Execute after a named interceptor | understudy |
Name | Description | Invoked by |
---|---|---|
setup |
Pre-start bootstrap setup | slay |
start |
Main application startup | slay |
routers |
Definition of app.routes |
slay |
actions |
Critical path application functionality | User |
A Stack
is a lightweight container for a set of before
and after
middlewares. This becomes very useful when you have potentially multiple routers in your application. A Stack
can be defined using app.stack
as follows:
middlewares.js
module.exports = function (app, options, next) {
//
// An authorization middleware for different roles
// returns an HTTP middleware function when invoked.
//
var authorize = require('./authorize');
//
// Stack middlewares can be declared and used inline
//
app.use(
app.stack({
name: 'admin-only',
before: [authorize('admin')]
}).middleware(function (req, res, next) {
// Dispatch (req, res) to a router.
})
);
//
// Or extended from a previous declaration and used inline
//
app.use(
app.stacks['designer-only']
.before(authorize('designer'))
.middleware(function (req, res, next) {
// Dispatch (req, res) to a router.
})
);
};
All Stack
instances created by invoking app.stack
will be exposed on the app.stacks
object.
- App is bootstrapped and
app.start([options], callback);
is invoked. app.perform('setup')
performsbefore
"setup" interceptors (see: understudy interceptors). This executes the built-inslay
preboots which:
- Creates
app.config
(an instance ofnconf.Provider
). - Creates
app.log
(an instance ofwinston.Logger
). - Creates user preboots (in your app
lib/preboots[.js]?
). This allows arbitrary user-defined preboots for extensibility in a sync or async fashion. - Schedules middlewares (in your app
lib/middlewares[.js]?
). - Schedules routes (in your app
lib/routes[.js]?
). - Schedules creation of default 404 route.
- Any
after
"setup" interceptors are invoked. (see: understudy interceptors).slay
runs nothing by default here. app.perform('start')
performsbefore
"start" interceptors (see: understudy interceptors). This executes the built-inslay
preboots which:
- Schedules defaults for after "routers"
- Invokes
app.perform('routers')
which performsbefore
"routers" interceptors, addsapp.routes
, and performsafter
"routers" interceptors. - Invokes user-defined preboots (in your app
lib/preboots[.js]?
). scheduled in (2) above. - Invokes user-defined middlewares (in your app
lib/middlewares[.js]?
) scheduled in (2) above. - Invokes user-defined routes (in your app
lib/routes[.js]?
) scheduled in (2) above. - Adds the final 404 handler on
app.routes
.
App.prototype._listen
is invoked which creates anyhttp
and/orhttps
servers.- Any
after
"start" interceptors are invoked. (see: understudy interceptors).slay
runs nothing by default here. - The
callback
fromapp.start([options], callback);
is invoked. Theapp
is now started and ready for use.
npm test
MIT