digitalbazaar/bedrock

Name: bedrock

Owner: Digital Bazaar, Inc.

Description: Bedrock: A core foundation for rich Web applications.

Created: 2014-12-04 20:57:14.0

Updated: 2017-10-04 03:04:55.0

Pushed: 2017-12-29 16:08:55.0

Homepage:

Size: 2805

Language: JavaScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Build Status Dependency Status

A core foundation for rich Web applications.

Overview

When creating a Web app, you need a foundation on which to build. There are a lot of disparate technologies out there that can be brought together into a cohesive whole to make this happen. The trouble is in finding, vetting, testing, and combining these technologies – all of which needs to happen before you can begin to make serious progress on your own project.

Bedrock helps you launch your ideas faster by bundling all the best-of-breed tooling that's necessary to build a modern, scalable Web app. It creates a solid foundation on which you can build, letting you focus on your project-specific needs.

Bedrock uses a modular design to help keep code well-organized and to allow an ecosystem to grow without unnecessary hindrance. Bedrock keeps its core simple: it provides a powerful configuration system, an event-based API, Linked Data-capabilities, and testing infrastructure that makes writing interactive modules easy. Bedrock is an opinionated, but flexible framework; it tells developers that there's one recommended way to accomplish a task, but if need be, a developer can go in another direction. Many of Bedrock's modules attempt to emulate this approach, creating organizing priniciples and clear guidelines for developers to follow that help break down problems and reduce cognitive load.

Bedrock uses node.js and runs on Linux, Mac OS X, and Windows. It can run on a low-powered laptop all the way up to an enterprise server.

Runnable Examples

A very basic, runnable “Hello World” bedrock example can be found at bedrock-hello-world.

More complex, runnable examples can be found at bedrock-examples.

Quick Examples
install bedrock

Create a MEAN stack application:

bedrock = require('bedrock');

odules
ire('bedrock-express');
ire('bedrock-docs');
ire('bedrock-i18n');
ire('bedrock-mongodb');
ire('bedrock-protractor');
ire('bedrock-request-limiter');
ire('bedrock-requirejs');
ire('bedrock-server');
ire('bedrock-session-mongodb');
ire('bedrock-validation');
ire('bedrock-views');

ock.start();

To include the AngularJS-based frontend, bower install these modules:

ock-angular
ock-angular-alert
ock-angular-filters
ock-angular-form
ock-angular-lazy-compile
ock-angular-modal
ock-angular-model
ock-angular-selector
ock-angular-ui

Create a simple express-based bedrock application:

bedrock = require('bedrock');

odules
ire('bedrock-express');

ock.events.on('bedrock-express.configure.routes', function(app) {
p.get('/', function(req, res) {
res.send('Hello World!');
;


ock.start();

Create a bedrock REST application with an express server, mongodb database, and mongodb-backed session storage:

bedrock = require('bedrock');

odules
ire('bedrock-express');
ire('bedrock-session-mongodb');
database = require('bedrock-mongodb');

ock.events.on('bedrock-mongodb.ready', function(callback) {
tabase.openCollections(['people'], function(err) {
if(err) {
  return callback(err);
}
callback();
;


ock.events.on('bedrock-express.configure.routes', function(app) {
p.get('/people', function(req, res, next) {
database.collections.people.find({}).toArray(function(err, docs) {
  if(err) {
    return next(err);
  }
  res.send(docs);
});
;

p.get('/people/:name', function(req, res, next) {
database.collections.people.findOne(
  {name: req.param('name')}, function(err, result) {
    if(err) {
      return next(err);
    }
    res.send(result);
  });
;

p.post('/people/:name', function(req, res){
database.collections.people.insert(
  [{name: req.param('name')}], function(err, result) {
    if(err) {
      return next(err);
    }
    res.send(result.result);
  });
;

p.delete('/people/:name', function(req, res){
database.collections.people.remove(
  {name: req.param('name')}, function(err, result) {
    if(err) {
      return next(err);
    }
    res.send(result.result);
  });
;


ock.start();

To create a MEAN stack application with identity management and authentication, see the bedrock-seed project.

Comprehensive Module Example

Below is an example that demonstrates Bedrock's event API. It creates a module with an http server that other modules can attach listeners to. It also registers a debug subcommand that displays the listeners that attached to the http server. The example also creates a module that attaches a simple “hello world” listener to the http server. The example demonstrates how to use Bedrock's event API to:

  1. Register a subcommand and handle it if is detected when the command line is parsed.
  2. Create a modular http server, listen to a privileged port (80), and emit a custom event to allow other modules to attach listeners to the server only after process privileges have been dropped.
  3. Execute custom behavior (eg: print the server's registered event listeners) after all other modules have started, if a subcommand was detected.
Module bedrock-example-server.js:
bedrock = require('bedrock');
http = require('http');

etup default module config
ock.config['example-server'] = {port: 80};

server = http.createServer();

mitted prior to command line parsing
ock.events.on('bedrock-cli.init', function() {
 add a new subcommand executed via: node project.js debug
r command = bedrock.program
.command('debug')
.description('display registered http listeners')
.option(
  '--debug-event <event>',
  'The event to print listeners for. [request]')
.action(function() {
  // save the parsed command information
  bedrock.config.cli.command = command;
});


mitted after the command line has been parsed
ock.events.on('bedrock-cli.ready', function() {
r command = bedrock.config.cli.command;
(command.name() !== 'debug') {
// `debug` not specified on the command line, return early
return;


 emitted after all bedrock.start listeners have run
drock.events.on('bedrock.ready', function() {
// print out all the listeners that registered with the server
var event = command.debugEvent || 'request';
var listeners = server.listeners(event);
console.log('listeners for event: ' + event);
listeners.forEach(function(listener, index) {
  console.log(index, listener.toString());
});
;


mitted before initialization, to allow any further configuration
ock.events.on('bedrock.configure', function() {
(bedrock.config.foo) {
bedrock.config.foo.bar = true;



mitted for early initialization, prior to dropping process privileges
ock.events.on('bedrock.admin.init', function(callback) {
 listen on port 80
rver.listen(bedrock.config['example-server'].port, function() {
// ready, call callback to allow bedrock to continue processing events
callback();
;


mitted for modules to do or schedule any unprivileged work on start up
ock.events.on('bedrock.start', function(callback) {
 emit a custom event giving other modules access to the example server
drock.events.emit('example.server.ready', server, function() {
callback();
;


mitted after all bedrock.ready listeners have run
ock.events.on('bedrock.started', function() {
nsole.log('everything is running now');

Module bedrock-example-listener.js:
bedrock = require('bedrock');

oad bedrock-example-server dependency
ire('./bedrock-example-server');

mitted to allow listeners to be attached to the example server
ock.events.on('example.server.ready', function(server) {
rver.on('request', function(request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World\n');
;

Example Main Project project.js:
bedrock = require('bedrock');

edrock modules to load
ire('./bedrock-example-server');
ire('./bedrock-example-listener');

hange the port to use
edrock.config['example-server'].port = 8123;

ock.start();

Run the main project and display the debug information with:

 project.js debug --debug-event request

Example output:

-03-05T21:59:23.727Z - info: starting bedrock...
-03-05T21:59:23.729Z - info: running bedrock master process pid=7705
-03-05T21:59:23.904Z - info: running bedrock worker process workerPid=7711
-03-05T21:59:23.906Z - info: startup time: 6ms workerPid=7711
eners for event: request
unction (request, response) {\n    response.writeHead(200, {\'Content-Type\': \'text/plain\'});\n    response.end(\'Hello World\\n\');\n  }'
ything is running now
Configuration

For documentation on Bedrock's core configuration, see config.js.

How It Works

Bedrock is a modular system built on node.js. Node.js modules typically communicate with each other using the CommonJS API (eg: require and module.exports, etc.), and Bedrock modules are no different. However, Bedrock also provides some additional low-level subsystems to help modules coordinate. These include: bedrock.config, bedrock.events, bedrock.jsonld, bedrock.loggers, bedrock.test, and bedrock.util.

To create a Bedrock project, all you need to do is create a JavaScript file, for example project.js, that requires bedrock, any other Bedrock modules you're interested in, and that then calls bedrock.start(). To run your project, run:

 project.js

If you're developing your project and you have installed all of the development packages for the Bedrock modules you're using, you can also easily test your project and any of the modules it has included by running:

 project.js test

This will execute Bedrock's built-in test framework, running all of the tests that each module has written. This approach ensures you're running tests for your entire project and its dependencies.

bedrock.config

Bedrock has a simple, but highly-customizable configuration system. All configuration variables are stored in a shared JavaScript object bedrock.config. The object is partitioned into separate configuration objects that are identified by the object's keys. For example Bedrock introduces the cli, core, constants, jsonld, and loggers object keys. The best practice for modules to claim their own area in the configuration object is to insert their default configuration object using a key that either matches their module name or that matches their module name minus any bedrock- prefix. For example, the bedrock-server module's specific configuration object can be found under bedrock.config.server. A mycompany-feature module would be found under bedrock.config['mycompany-feature']. Modules may define whatever configuration variables they want to using whatever format is appropriate for their own use.

The bedrock.util module has helpers to setup configurations, and in particular, dynamically computed configurations. Computed values can help to simplify dependency issues by allowing values to be computed at runtime from a function or string template. (Note there is a small cost with computed config values which could be important depending on the use case.)

bedrock.util.config.Config creates a wrapper around a config object and optional options. This wrapper exposes a new, helpful API that is detailed below. A common setup could look like the following.

n empty config object
config = {};
ommon options
options = {
 the config
nfig: config
 local vars used during string template evaluation
cals: config

rap the config
c = new bedrock.util.config.Config(config, options);

Bedrock provides a shared wrapper around the common bedrock.config as bedrock.util.config.main.

To do simple sets of config data, use the set() API.

c = bedrock.util.config.main;
et a config variable with a path
ath components are created as needed
t('server.port', 8443);
onfig is now {"server": {"port": 8443}}

Normal code and the config API can be mixed. A useful helper is setDefault(). This call lets you simplify ensuring a full object path exists before setting data. Objects in the path are created as needed.

config = bedrock.config;
c = bedrock.util.config.main;
reate container object if needed
tDefault('accounts.admin', {});
he config is just a normal object
ig.accounts.admin.name = 'Ima Admin';
t('accounts.admin.id', 1);
he config object is returned from setDefault()
account123 = c.setDefault('accounts.account123', {});
unt123.id = 123;
unt123.name = 'Account 123';

Computed config values using the setComputed() API add on a much more powerful feature where values will be calculated at runtime.

config = bedrock.config;
et the Config wrapper for the default bedrock.config
c = bedrock.util.config.main;
et static values
t('server.port', 8443);
t('server.domain', 'bedrock.dev');
et a computed value that uses values from the main config
tComputed('server.host', () => {
turn config.server.domain + ':' + config.server.port;

ole.log(config.server.host); // "bedrock.dev:8443"

The logic for a computed value can be any normal code. If source config values are updated the computed values will reflect the change.

config = bedrock.config;
c = bedrock.util.config.main;
t('server.port', 8443);
t('server.domain', 'bedrock.dev');
tComputed('server.host', () => {
 only add the port if it's not the well known default
(config.server.port !== 443) {
return config.server.domain + ':' + config.server.port;

turn config.server.domain;

ole.log(config.server.host); // "bedrock.dev:8443"
t('server.port', 443);
ole.log(config.server.host); // "bedrock.dev"

setComputed() can be verbose. a wrapper can be created using the standard bind() functionality. A helper called computer() will do this for you.

config = bedrock.config;
cc = bedrock.util.config.main.computer();
server.host', () => {
 only add the port if it's not the well known default
(config.server.port !== 443) {
return config.server.domain + ':' + config.server.port;

turn config.server.domain;

Computed values can also be created with lodash-style string templates.

config = bedrock.config;
cc = bedrock.util.config.main.computer();
server.baseUri', 'https://${server.host}');
ole.log(config.server.baseUri); // "https://bedrock.dev:8443"
se locals option to simplify templates
base.computed', '${base.a}:${base.b}:${base.c}');
base.computed', '${a}:${b}:${c}', {locals: config.base});

Setting or computing multiple values with one call uses an object notation:

c = bedrock.util.config.main;
cc = c.computer();
t({
erver.port': 8443,
erver.domain': 'bedrock.dev',
erver.name': 'Bedrock Dev',
sers.admin.id': 1


erver.url': 'https://${server.domain}:${server.port}',
sers.admin.url': '${server.url}/users/${users.admin.id}'

Computed values can be added to an array using indexing or the pushComputed feature. If indexing is used the array must already exist or the {parentDefault: []} option should be used. pushComputed will create the parent array if needed.

config = bedrock.config;
c = bedrock.util.config.main;
cc = c.computer();
server.baseUri', 'https://${server.host}');
tDefault('resources', []);
resources[0]', '${server.baseUri}/r/0');
shComputed('resources', '${server.baseUri}/r/1');
bedrock.events

It's sometimes necessary to allow modules to coordinate with each other in an orderly fashion. To achieve this, Bedrock provides an event API. Bedrock's event API is very similar to node's built-in EventEmitter, but it provides a few additional features.

In particular, when emitting an event, Bedrock can wait for a listener to run asynchronous code before executing the next listener. This allows each listener to run synchronously or asynchronously, depending on their individual needs, without worrying that the next listener or the next event will be emitted before they have completed what they need to do.

Bedrock's event system also provides another feature, which is the ability to cancel events. Event cancelation allows modules to build-in default behavior that can be canceled by other modules. Whenever a synchronous listener returns false or an asynchronous listener passes false to its callback, the event will not be emitted to the remaining listeners, and, if a callback was given when the event was emitted, it will be given the false value allowing the emitter to take a different action.

To a emit an event:

ock.events.emit('example-module.foo', data, function(err, result) {
(err) {
console.log('an error occurred in a listener and the event was canceled');
return;

(result === false) {
console.log('the event was canceled, but not due to an error');
return;

nsole.log('the event was not canceled');

To create a synchronous listener:

ock.events.on('example-module.foo', function(data) {
(anErrorOccured) {
throw new Error('foo');

(shouldCancel) {
return false;

 do something synchronous

To create an asynchronous listener:

ock.events.on('example-module.foo', function(data, callback) {
 because an additional parameter was added to the listener function,
 it is assumed it should be a function and a callback will be passed
 that *must* be called
(anErrorOccurred) {
return callback(new Error('foo'));

(shouldCancel) {
return callback(null, false);

 do something asynchronous, other listeners won't execute and event
 emission won't continue until you call the callback
ocess.nextTick(function() {
callback();
;

Note that the asynchronous analog for throwing an error is calling the callback with an error as its first parameter and the analog for returning a value (typically only used for event cancelation) is to pass null for the first parameter and the return value for the second parameter of the callback. This API matches the “error-first” callback continuation-style that is standard practice in node.

Bedrock core emits several events that modules may listen for. These events fall into three possible namespaces: bedrock-cli, bedrock-loggers and bedrock. The bedrock-cli events are emitted to allow coordination with Bedrock's command line interface. The bedrock-loggers.init event is emitted after the bedrock-cli.init event. The bedrock events are emitted after all the bedrock-cli events, unless a listener cancels the bedrock-cli.ready event or causes the application to exit early.

bedrock.jsonld

Bedrock is intended to provide a foundation for Linked Data applications, and as such, it provides a JSON-LD processor (via jsonld.js) that is integrated with its configuration system. Any JSON-LD context that is inserted into the bedrock.config.constants.CONTEXTS object (where keys are the URL for the context and the values are the context itself), will be served from disk instead of retrieved from the Web. This is a useful feature for both developing Linked Data applications and for ensuring contexts are available in offline mode.

bedrock.loggers

Bedrock has a built-in logging subsystem based on winston. Anything you can do with winston, you can do with Bedrock. Bedrock provides a set of default loggers that are suitable for most applications. The main application logger can be retrieved via bedrock.loggers.get('app'). A call to bedrock.loggers.addTransport can be made in event handlers of the bedrock-loggers.init event to add new winston transports. Logging categories (such as app) and the transports used by them can be configured via bedrock.config.

Bedrock supports multi-level child loggers with common metadata. These are created with bedrock.loggers.get('app').child({...}). Provided metadata will be added to child log output. A special module meta name can optionally be used for pretty output. A shortcut for creating named module loggers is bedrock.loggers.get('app').child('name').

Module prefix display can be controlled per-category:

et a child logger with custom module name
logger = bedrock.loggers.get('app').child('my-module');

essage module prefix controlled with a per-category config value
ock.config.loggers.app.bedrock.modulePrefix = false;
er.info('an info message');
odule displayed as normal meta data:
017-06-30T12:34:56.789Z - info: an info message workerPid=1234, module=my-module

ith module prefix enabled:
ock.config.loggers.app.bedrock.modulePrefix = true;
er.info('an info message');
isplayed as an nice message prefix:
017-06-30T12:34:56.789Z - info: [my-module] an info message workerPid=1234
bedrock.test

Bedrock comes with test support built-in. It provides a test framework based on mocha that integrates with bedrock.config. To add a mocha test to a Bedrock module, you simply push a directory or a file path onto the config.mocha.tests array. Bedrock also makes it easy to add other test frameworks via Bedrock modules. For example, bedrock-protractor integrates the AngularJS protractor test framework with Bedrock. Whenever you run tests against your project, your project and all of its dependencies will be tested, using whatever test frameworks they have registered with. Bedrock also provides command line options to limit the tests that run as desired.

bedrock.util

Bedrock provides a number of helpful general purpose utilities. For example, Bedrock defines a BedrockError class that extends the default Error class. A BedrockError can keep track of a series of “causes” (other errors) that allow developers to better understand why an error occured. BedrockErrors can also be marked as public, which allows modules that may, for example, serve error information over the Web to display more error details. bedrock.util also contains tools for formatting dates, extending/merging/cloning objects, and generating UUIDs.

Recommended Modules

bedrock-server provides a core, cluster-based HTTPS server.

bedrock-express provides an Express server with reasonable defaults and extra features like the ability to layer static files and directories to support overrides.

bedrock-mongodb provides an API for connecting to a MongoDB database and creating and using collections.

bedrock-jobs provides a background job scheduler.

bedrock-requirejs provides a client-side module loader and autoconfiguration capabilities for bower components.

bedrock-views provides server-rendered views with HTML5 + Bootstrap3.

bedrock-angular layers on top of bedrock-views to provide client-rendered AngularJS views.

bedrock-idp provides user identity and public key management.

bedrock-protractor integrates protractor with Bedrock, exposing a powerful end-to-end AngularJS test framework to Bedrock modules.

Other Bedrock modules provide REST APIs, user account management, strong cryptography support, DoS protection, digital signatures, Linked Data, and tons of other FEATURES. If you don't need all the fancy features, Bedrock is modular, so you can use only the modules you want.

Quickstart

You can follow the following tutorial to setup and use Bedrock on a Linux or Mac OS X development machine.

Requirements
Running Bedrock

Run the following to start up a development server from the source directory:

node index.js

To add more verbose debugging, use the --log-level option:

node index.js --log-level debug
Running the Tests

Run all tests:

npm test

Run only the mocha test framework:

node index.js test --framework mocha

Run a specific mocha test:

node index.js test --framework mocha --mocha-test tests/test.js
Running the Code Coverage Tool
npm run coverage

Look at 'coverage.html' using a web browser

Features

For an example list of features provided by Bedrock modules, see the FEATURES file.

FAQ

See the FAQ file for answers to frequently asked questions.

Hacking

See the CONTRIBUTING file for various details for coders about hacking on this project.

Authors

See the AUTHORS file for author contact information.

License

Bedrock and all Bedrock modules are:

Copyright (c) 2011-2015 Digital Bazaar, Inc.
All Rights Reserved

You can use Bedrock for non-commercial purposes such as self-study, research, personal projects, or for evaluation purposes. See the LICENSE file for details about the included non-commercial license information.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.