This module framework helps make creating scalable applications quick and simple, focusing on configuration and flexibility over boilerplate.
The core application module handles:
- Multi-environment, extendable application configuration
- Error reporting (optionally, via Sentry.io)
- Connection and start up initialization handling
- Utility functions
The principle here is that an application is built upon okanjo-app, leveraging other okanjo modules to interface with various services.
Add to your project like so:
npm install okanjo-app
A very simple implementation could look like this:
const OkanjoApp = require('okanjo-app');
const config = {
yourThing: {
yourKey: 'yourValue'
},
reportToSentry: false,
// ravenReportUri: 'http://[email protected]/number',
// ravenOptions: {...}
};
const app = new OkanjoApp(config);
// Additional initialization, plugin registering, etc
// Go!
await app.connectToServices();
We generally recommend creating a config.js
file, that exports a configuration object.
my_app/
+- node_modules/
+- routes/
+- config.js
+- index.js
+- package.json
config.js:
const Path = require('path');
module.exports = {
webServer: {
port: 3000,
routePath: Path.join(__dirname, 'routes')
},
reportToSentry: false,
// ravenReportUri: 'https://your-reporting-uri',
// ravenOptions: {...}
production: {
webServer: {
port: 80
},
reportToSentry: true
}
};
index.js:
const OkanjoApp = require('okanjo-app');
const app = new OkanjoApp(config);
app.connectToServices().then(() => {
// Everything connected, ready to do your thing!
});
Then you can start the app like so:
- Default environment (e.g. locally):
node .
- Production environment:
env=production node .
app.config
– The effective configuration of the application.app.currentEnvironment
– The name of the current application environment. (e.g.default
)app.gracefulShutdown
– Flag your application may use to indicate the app should be shutting down.app.ravenClient
– Raven client instanceapp.ready
– Boolean indicating whetherapp.connectToServices
was called and completed.app.reportingContext
– Object whose key-value pairs are copied to error reports.
const app = new OkanjoApp(config)
config
: Object with key-values or nested environment configurations
Starts initialization for any registered services and fires the callback once all are completed.
callback(err)
: Function that is fired when application modules finish initializing.err
– if a service connector returns an error, it'll stop and get caught here
- Returns a
Promise
, so you can await it
This method is internally called when constructing. You may use this method to enable reporting if initially disabled.
enabled
: Boolean flag to turn reporting on (true
) or off (false
)
Attaches the given keys within context
to the reporting context, which is attached to any future error reports.
This is useful for setting global information on error reports, such as environment, application, current server, etc.
context
: Object with key-value pairs.
Used to report application errors or bad things to stderr and if configured, Sentry.
This method will take any number of parameters.
The last Error
object encountered will be selected as the error being reported.
If no Error
object is provided, one will be generated using the first string
argument provided.
For example:
myService.connect((err) => {
if (err) {
app.report('Failed to connect to my service!', err, { additional: 'context'});
}
// ...
});
Other example usages:
await app.report(err)
- just report an error with no contextawait app.report('what happened', err)
- report what happened with the original errorawait app.report('what happened', err, { arg, context, whatever })
- report what happened and provide additional data, detailing what the state was at the time of the error
Takes any number of arguments, and for each, prints each to stderr with colors and max depth of 5. Useful for quickly debugging or reporting information about a complex object.
Example usages:
app.dump(myObject)
app.dump(myThing, myOtherThing, 42)
A wrapper around console.log that writes to stderr and can be conditionally disabled by setting environment variable SILENCE_REPORTS=1
.
Useful for making various logging tasks optional.
Example usages:
app.log('hey look at this thing, thing)
;
Deep copies key-values from source
to destination
. Traverse arrays and objects recursively.
source
– Source object to copy fromdestination
– Destination object to copy into. If not truthy, it will be made into an object or array.
For example:
const source = {
a: 1,
b: {
hi: 'there'
},
e: null
};
const destination = {
c: true,
b: { there: 'hi' },
d: null
};
app.copy(destination, source);
/* destination now looks like:
{
a: 1,
b: { hi: 'there', there: 'hi' },
c: true,
d: null,
e: null
}
*/
Useful for flattening a nested object into a single level object.
input
– The object to flattenoptions
– Optional. Flags that can affect the output. **options.dateToIso
– If truthy, then date objects will be flattened into an ISO string.
MongoDB ObjectId's will be converted to hex strings.
For example:
app.flattenData({ a: { b: 1, c: { d: [2,3,4] }}});
/* returns:
{
a_b: 1,
a_c_d_0: 2,
a_c_d_1: 3,
a_c_d_2: 4
}
*/
OkanjoApp extends Hapi Boom. All boom methods are available as possible responses. Since Boom is great for error response building, OkanjoApp extends it to add consistent 'positive' responses.
For example:
app.response.badRequest('You goofed');
/* Reply shows:
{
"statusCode": 400,
"error": "Bad Request",
"message": "You goofed"
}
*/
Creates a new 200/ok response with the given data payload.
data
– value to output as data in the response.h
– Optional, response toolkit. Internally does: h.response(data).code(200)
For example:
app.response.ok({ hello: 'world' });
/* Reply shows:
{
"statusCode": 200,
"error": null,
"data": { "hello": "world" }
}
*/
Creates a new 201/created response with the given data payload.
data
– value to output as data in the response.h
– Optional, response toolkit. Internally does: h.response(data).code(201)
For example:
app.response.created({ hello: 'world' });
/* Reply shows:
{
"statusCode": 201,
"error": null,
"data": { "hello": "world" }
}
*/
Useful helper function for converting internal data objects into ones suitable for api output.
obj
– Object or array of objects to formatclosure(obj)
– Function which is expected to format and return the a single object
Note: This method automatically attaches obj.created
and obj.updated
is present in obj
.
For example:
function formatter(mixed) {
return app.response.formatForResponse(mixed, (obj) => {
return {
id: "thing_" + obj._id,
name: obj.name || "Untitled",
// ...
};
});
}
formatter({
_id: "1",
name: "thing 1",
secret: "you should not see me",
created: new Date(),
updated: null
})
/*
{ id: 'thing_1',
name: 'thing 1',
created: 2017-11-07T18:26:09.022Z,
updated: null }
*/
formatter({
_id: "2",
name: null,
secret: "you should not see me"
})
/*
{ id: 'thing_2', name: 'Untitled' }
*/
Fires callback
when the app has finished connecting to all services and is ready.
Fires callback(err)
when a service connector fails on connectToServices()
Our goal is quality-driven development. Please ensure that 100% of the code is covered with testing.
Before contributing pull requests, please ensure that changes are covered with unit tests, and that all are passing.
To run unit tests and code coverage:
npm run report
This will perform:
- Unit tests
- Code coverage report
- Code linting
Sometimes, that's overkill to quickly test a quick change. To run just the unit tests:
npm test
or if you have mocha installed globally, you may run mocha test
instead.