microboot
v3.0.3
Published
Boot up your app in wee little modules with the help of glob.
Downloads
36
Readme
microboot
Boot up your app in wee little modules with the help of glob.
const system = await microboot([
'boot/databases',
'boot/logging',
'api/**',
'listeners'
])
console.log('Service booted!')
Contents
Introduction
microboot is a small helper designed to facilitate booting up complex applications that we all have. Database connections need to be made, logging services initialised and it all ends up happening nastily at the top of the file.
Using microboot helps you organise your start-up by initialising your app step-by-step, reading file structures to grab everything you need.
Uses
I use this tool for booting APIs and microservices within our architecture. microboot lends itself really well to this as each endpoint can be instantiated in a single, isolated file and the only configuration needed to hook these together is microboot.
// index.js
require('microboot')('endpoints')
// connections/api.js
const express = require('express')
const api = express()
api.listen(3000)
module.exports = api
// endpoints/get/list.js
const api = require('../../connections/api')
module.exports = () => {
api.get('/list', handler)
}
function handler (req, res) => {...}
In that example:
index.js
(our entry point) triggers microboot to grab everything in theendpoints
folderendpoints/get/list.js
is found andrequire
dconnections/api.js
isrequire
d which set ups a single Express APIendpoints/get/list.js
adds a GET endpoint- :tada:
While this is a very simple example and it could arguably be clearer having everything in a single file, the key here is extensibility. Having each endpoint separated into an individual file means adding or removing endpoints is as simple as adding or removing a file. No extra work needed.
How it works
In your main file, use microboot as it's used above, specifying a single path or multiple paths of files you want to run in the order you want them to run in. Each element in the given array is a different "stage" and files within each are sorted alphabetically to run. Here's our example:
var microboot = require('microboot')
microboot([
'boot/databases',
'utils/logging.js',
'lib/endpoints/**'
]).then((arg) => {
console.log('Boot complete!')
}).catch((err) => {
console.error('Something went wrong:', err)
process.exit(1)
})
In the files you choose to run, ensure they export a function that will be triggered when microboot iterates through.
You can optionally map two parameters: one that's passed through all functions, allowing you to build the object as it goes through and system and the done
argument which makes the step asynchronous.
You can also return a promise to make your step asynchronous. Here are some examples:
// boot/databases/mongodb.js
// an asynchronous stage
module.exports = function mongodb (arg, done) {
connectToMongoDb(function (connection) {
arg.mongo = connection
return done()
})
}
// lib/endpoints/post/login.js
// a synchronous stage
module.exports = function post_login (arg) {
arg.api.newAppEndpoint('post', '/login')
}
// lib/endpoints/goDb/setup.js
// a promise stage, assuming goDb.setup() returns a promise
module.exports = () => {
return goDb.setup()
}
You're set! microboot will now first run all JS files found in the boot/databases
folder (recursively) including our mongodb.js
, then specifically utils/logging.js
, then all JS files found in the lib/endpoints
folder (recursively) including our login.js
.
If you want to know more about the syntax used for specifying recursiveness and the like, take a look at glob; it's what's behind microboot's loader, microloader.
Failing to initialise
If something screws up, you should want to stop your app starting. If that's the case, you can throw an error during a step to stop things in their tracks.
For a synchronous step, just go ahead and throw:
module.exports = function my_broken_api () {
throw new Error('Oh no! It\'s all gone wrong!')
}
For a callback step, return your error as the first argument of the callback:
module.exports = function my_broken_api (arg, done) {
startUpApi(function (err) {
if (err) {
return done(err)
}
return done()
})
}
For a promise step, either reject your promise or throw if you're running an async function:
module.exports = () => {
return new Promise((resolve, reject) => {
reject(new Error('Oh no!'))
})
}
// or
module.exports = async () => {
throw new Error('Oh no!')
}
Examples
Yay examples! These all assume the following directory tree, the root representing your project's current working directory.
.
├── bin
│ └── example
├── boot
│ ├── 01_logging
│ │ ├── bunyan.js
│ │ └── postal.js
│ ├── 02_amqp.js
│ └── 03_database.js
├── index.js
├── lib
│ ├── types
│ │ ├── adding.js
│ │ ├── dividing.js
│ │ ├── multiplying.js
│ │ └── subtracting.js
│ └── utils
│ ├── 404.png
│ ├── deleteFile.js
│ ├── doNothing.js
│ ├── getFile.js
│ └── hugFile.js
├── package.json
└── test
└── test.js
7 directories, 17 files
Running everything in boot
Runs in order:
bunyan.js
,postal.js
,amqp.js
,database.js
microboot('boot')
Running everything in boot
, then all utils
Runs in order:
bunyan.js
,postal.js
,amqp.js
,database.js
,deleteFile.js
,doNothing.js
,getFile.js
,hugFile.js
microboot(['boot', 'lib/utils'])
Running 01_logging
after 02_amqp.js
and 03_database.js
Runs in order:
02_amqp.js
,03_database.js
,bunyan.js
,postal.js
microboot(['boot/*', 'boot/logging'])
API reference
microboot(stages, [arg], [callback])
Arguments
stages
- A single file path or array of file paths (from the CWD) from which to load Microboot's list of functions to run.arg
- Optional, defaults toundefined
A single argument that is passed through every function run, allowing you to mutate it to build up the service as it boots. Ifarg
is a function and nocallback
has been provided,arg
will instead be used as the callback.callback(arg)
- Optional The function to run once all stages have been successfully run. Is passed the final, mutatedarg
.
Returns
- If
callback
defined,undefined
. - If
callback
not defined, returns aPromise
resolving witharg
or rejecting with anError
.
- If
stage([arg], [callback])
arg
- Optional The arg that's being passed through each stage run. Can be specified in themicroboot
call or defaults toundefined
.callback(err)
- Optional If this is mapped to your stage function the stage will be treated as asynchronous and will require that this callback is run before moving to the next one. If there's an error, pass it back as the first parameter. If you wish to return aPromise
, return one and do not specify thecallback
.
Debugging
If microboot doesn't seem to be behaving properly, set the DEBUG=microboot*
environment variable, run your app and create a new issue with those logs.