diff --git a/README.md b/README.md
index 0e6c6d4..2b3c406 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ LoopBack Boot is a convention-based bootstrapper for LoopBack applications.
**For full documentation, see the official StrongLoop documentation:**
- * [Creating a LoopBack application](http://docs.strongloop.com/display/DOC/Creating+a+LoopBack+application)
+ * [Creating a LoopBack application](http://docs.strongloop.com/display/LB/Creating+a+LoopBack+application)
## Installation
@@ -23,7 +23,7 @@ app.use(loopback.rest());
app.listen();
```
-See [API docs](http://apidocs.strongloop.com/loopback-boot/) for
+See [API docs](http://apidocs.strongloop.com/loopback-boot/#api) for
complete API reference.
## Versions
@@ -34,3 +34,358 @@ up to slc version 2.5.
The version range `2.x` supports the new project layout as scaffolded by
`yo loopback`.
+
+This document describes the configuration conventions of the `2.x` versions.
+See [Migrating from 1.x to 2.x](http://apidocs.strongloop.com/loopback-boot/#migrating-from-1x-to-2x)
+for step-by-step instructions on how to upgrade existing projects.
+
+## Configurations and conventions
+
+The bootstrapping process takes care of the following tasks:
+
+ - Configuration of data-sources.
+ - Definition of custom Models
+ - Configuration of models, attaching models to data-sources.
+ - Configuration of app settings like `host`, `port` or `restApiRoot`.
+ - Running additional boot scripts, so that the custom setup code can be kept
+ in multiple small files as opposed to keeping everything in the main app file.
+
+Below is the typical project layout. See the following sections for description
+of the project files.
+
+```
+project/
+ app.js
+ config.json
+ datasources.json
+ models.json
+ models/
+ boot/
+```
+
+### App settings
+
+The settings are loaded from the file `config.json` in the project root directory
+and can be accessed via `app.get('option-name')` from the code.
+
+Additionally, the following files can provide values to override `config.json`:
+
+ - `config.local.js` or `config.local.json`
+ - `config.{env}.js` or `config.{env}.json`, where `{env}` is the value of `NODE_ENV`
+ (typically `development` or `production`)
+
+**NOTE:** The additional files can override the top-level keys with
+value-types (strings, numbers) only. Nested objects and arrays are
+not supported at the moment.
+
+#### Example settings
+
+*config.json*
+
+```json
+{
+ "host": "localhost",
+ "port": 3000,
+ "restApiRoot": "/api"
+}
+```
+
+*config.production.js*
+
+```js
+module.exports = {
+ host: process.env.CUSTOM_HOST,
+ port: process.env.CUSTOM_PORT
+};
+```
+
+### Data sources
+
+The configuration of data sources is loaded from the file `datasources.json`
+in the project root directory, the data sources can be accessed via
+`app.datasources['datasource-name']` from the code.
+
+Additionally, the following files can provide values to override
+`datasources.json`:
+
+ - `datasources.local.js` or `datasources.local.json`
+ - `datasources.{env}.js` or `datasources.{env}.json`,
+ where `{env}` is the value of `NODE_ENV`
+ (typically `development` or `production`)
+
+**NOTE:** The additional files can override the top-level data-source options
+with value-types (strings, numbers) only. Nested objects and arrays are
+not supported at the moment.
+
+#### Example data sources
+
+*datasources.json*
+
+```js
+{
+ // the key is the datasource name
+ // the value is the config object to pass to
+ // app.dataSource(name, config).
+ db: {
+ connector: 'memory'
+ }
+}
+```
+
+*datasources.production.json*
+
+```js
+{
+ db: {
+ connector: 'mongodb',
+ database: 'myapp',
+ user: 'myapp',
+ password: 'secret'
+ }
+}
+```
+
+### Models: definition
+
+Custom models are defined using JSON files in `models/` directory,
+one JSON file per model.
+
+#### Example models
+
+The following are example JSON files for two `Model` definitions:
+`Dealership` and `Location`.
+
+*models/dealership.json*
+
+```js
+{
+ // the model name
+ "name": "Dealership",
+ // the options passed to Model.extend(name, properties, options)
+ "options": {
+ "relations": {
+ "cars": {
+ "type": "hasMany",
+ "model": "Car",
+ "foreignKey": "dealerId"
+ }
+ }
+ },
+ // the properties passed to Model.extend(name, properties, options)
+ "properties": {
+ "id": {"id": true},
+ "name": "String",
+ "zip": "Number",
+ "address": "String"
+ }
+}
+```
+
+*models/car.json*
+```js
+{
+ "name": "Car",
+ // options can be specified at the top level too
+ "relations": {
+ "dealer": {
+ "type": "belongsTo",
+ "model": "Dealership",
+ "foreignKey": "dealerId"
+ },
+ }
+ "properties": {
+ "id": {
+ "type": "String",
+ "required": true,
+ "id": true
+ },
+ "make": {
+ "type": "String",
+ "required": true
+ },
+ "model": {
+ "type": "String",
+ "required": true
+ }
+ }
+}
+```
+
+#### Adding custom methods to models
+
+The models created from JSON files come with the set of built-in methods
+like `find` and `create`. To implement your custom methods, you should
+create a javascript file in `models/` directory with the same base-name
+as the JSON file containing model definition (e.g. `models/car.js` for
+`models/car.json`) and define the methods there.
+
+Example:
+
+*models/car.js*
+
+```js
+// Car is the model constructor
+// Base is the parent model (e.g. loopback.PersistedModel)
+module.exports = function(Car, Base) {
+ // Define a static method
+ Car.customMethod = function(cb) {
+ // do some work
+ cb();
+ };
+
+ // Define an instance (prototype) method
+ Car.prototype.honk = function(duration, cb) {
+ // make some noise for `duration` seconds
+ cb();
+ };
+
+ // Provide a custom setup method
+ Car.setup = function() {
+ Base.setup.call(this);
+
+ // configure validations,
+ // configure remoting for methods, etc.
+ };
+};
+```
+
+### Models: configuration
+
+Before the models can be used in a loopback application, they have to be
+configured - attached to a data-source, exposed via the REST API, and so on.
+
+The configuration is described in the file `models.json`:
+
+```js
+{
+ // the key is the model name
+ "Dealership": {
+ // a reference, by name, to a dataSource definition
+ "dataSource": "my-db"
+ },
+ "Car": {
+ "dataSource": "my-db",
+ // do not expose Car over the REST API
+ "public": false
+ }
+}
+```
+
+The bootstrapper will automatically load definition of every custom model
+configured in `models.json`. By default, the definition files are loaded from
+`models/` subdirectory. However, it is possible to specify a different location
+(or even multiple locations) via `_meta.sources`:
+
+```js
+{
+ "_meta": {
+ "sources": [
+ // all paths are relative to models.json
+ "./models"
+ "./node_modules/foobar/models"
+ ]
+ },
+ // use the `FooBar` model from the `foobar` module
+ "FooBar": {
+ "dataSource": "db"
+ }
+}
+```
+
+### Boot scripts
+
+When the data sources and models are configured, the bootstrapper invokes
+all scripts in the `boot/` folder. The scripts are sorted lexicographically
+ingoring case.
+
+#### Example boot script
+
+*boot/authentication.js*
+
+```js
+module.exports = function(app) {
+ app.enableAuth();
+};
+```
+
+## Running in a browser
+
+The bootstrap process is implemented in two steps that can be called
+independently.
+
+### Build
+
+The first step loads all configuration files, merges values from additional
+config files like `app.local.js` and produces a set of instructions
+that can be used to boot the application.
+
+These instructions must be included in the browser bundle together
+with all configuration scripts from `models/` and `boot/`.
+
+Don't worry, you don't have to understand these details.
+Just call `boot.compileToBrowserify`, it will take care of everything for you.
+
+*build file (Gruntfile.js, gulpfile.js)*
+
+```js
+var browserify = require('browserify');
+var boot = require('loopback-boot');
+
+var b = browserify({
+ basedir: appDir,
+});
+
+// add the main application file
+b.require('./browser-app.js', { expose: 'loopback-app' });
+
+// add boot instructions
+boot.compileToBrowserify(appDir, b);
+
+// create the bundle
+var out = fs.createWriteStream('browser-bundle.js');
+b.bundle().pipe(out);
+// handle out.on('error') and out.on('close')
+```
+
+### Run
+
+In the browser, the main application file should call loopback-boot
+to setup the loopback application by executing the instructions
+contained in the browser bundle:
+
+*browser-app.js*
+
+```js
+var loopback = require('loopback');
+var boot = require('loopback-boot');
+
+var app = module.exports = loopback();
+boot(app);
+```
+
+The app object created above can be accessed via `require('loopback-app')`,
+where `loopback-app` is the identifier used for the main app file in
+the browserify build shown above.
+
+Here is a simple example demonstrating the concept:
+
+*index.html*
+
+```xml
+
+
+```
diff --git a/docs.json b/docs.json
index 05f6402..5607f8f 100644
--- a/docs.json
+++ b/docs.json
@@ -1,12 +1,12 @@
{
"content": [
+ "README.md",
{
- "title": "Bootstrap API",
+ "title": "API",
"depth": 2
},
"index.js",
"browser.js",
- "docs/configuration.md",
- "docs/browserify.md"
+ "docs/migrating-from-1x-to-2x.md"
]
}
diff --git a/docs/browserify.md b/docs/browserify.md
deleted file mode 100644
index 9873026..0000000
--- a/docs/browserify.md
+++ /dev/null
@@ -1,74 +0,0 @@
-## Running in a browser
-
-The bootstrap process is implemented in two steps that can be called
-independently.
-
-### Build
-
-The first step loads all configuration files, merges values from additional
-config files like `app.local.js` and produces a set of instructions
-that can be used to boot the application.
-
-These instructions must be included in the browser bundle together
-with all configuration scripts from `models/` and `boot/`.
-
-Don't worry, you don't have to understand these details.
-Just call `boot.compileToBrowserify`, it will take care of everything for you.
-
-```js
-/*-- build file --*/
-var browserify = require('browserify');
-var boot = require('loopback-boot');
-
-var b = browserify({
- basedir: appDir,
-});
-
-// add the main application file
-b.require('./app.js', { expose: 'loopback-app' });
-
-// add boot instructions
-boot.compileToBrowserify(appDir, b);
-
-// create the bundle
-var out = fs.createWriteStream('app.bundle.js');
-b.bundle().pipe(out);
-// handle out.on('error') and out.on('close')
-```
-
-### Run
-
-In the browser, the main application file should call loopback-boot
-to setup the loopback application by executing the instructions
-contained in the browser bundle:
-
-```js
-/*-- app.js --*/
-var loopback = require('loopback');
-var boot = require('loopback-boot');
-
-var app = module.exports = loopback();
-boot(app);
-```
-
-The app object created above can be accessed via `require('loopback-app')`,
-where `loopback-app` is the identifier used for the main app file in
-the browserify build shown above.
-
-Here is a simple example demonstrating the concept:
-
-```xml
-
-
-```
diff --git a/docs/configuration.md b/docs/migrating-from-1x-to-2x.md
similarity index 56%
rename from docs/configuration.md
rename to docs/migrating-from-1x-to-2x.md
index d209bb7..0f3177e 100644
--- a/docs/configuration.md
+++ b/docs/migrating-from-1x-to-2x.md
@@ -1,105 +1,4 @@
-## Configuration and conventions
-
-### Model Definitions
-
-The following two examples demonstrate how to define models.
-
-*models/dealership.json*
-
-```json
-{
- "name": "dealership",
- "relations": {
- "cars": {
- "type": "hasMany",
- "model": "Car",
- "foreignKey": "dealerId"
- }
- },
- "properties": {
- "id": {"id": true},
- "name": "String",
- "zip": "Number",
- "address": "String"
- }
-}
-```
-
-*models/car.json*
-
-```json
-{
- "name": "car",
- "properties": {
- "id": {
- "type": "String",
- "required": true,
- "id": true
- },
- "make": {
- "type": "String",
- "required": true
- },
- "model": {
- "type": "String",
- "required": true
- }
- }
-}
-```
-
-To add custom methods to your models, create a `.js` file with the same name
-as the `.json` file:
-
-*models/car.js*
-
-```js
-module.exports = function(Car, Base) {
- // Car is the model constructor
- // Base is the parent model (e.g. loopback.PersistedModel)
-
- // Define a static method
- Car.customMethod = function(cb) {
- // do some work
- cb();
- };
-
- Car.prototype.honk = function(duration, cb) {
- // make some noise for `duration` seconds
- cb();
- };
-
- Car.setup = function() {
- Base.setup.call(this);
-
- // configure validations,
- // configure remoting for methods, etc.
- };
-}
-```
-
-### Model Configuration
-
-The following is an example JSON configuring the models defined above
-for use in an loopback application.
-
-`dataSource` options is a reference, by name, to a data-source defined
-in `datasources.json`.
-
-*models.json*
-
-```json
-{
- "dealership": {
- "dataSource": "my-db",
- },
- "car": {
- "dataSource": "my-db"
- }
-}
-```
-
-### Migrating from 1.x to 2.x
+## Migrating from 1.x to 2.x
**Starting point: a sample 1.x project**
@@ -136,7 +35,22 @@ var app = loopback();
boot(app, __dirname);
```
-#### Model definitions & configurations
+### App settings
+
+The files with applications settings were renamed from `app.*` to `config.*`.
+Rename the following files to upgrade a 1.x project for loopback-boot 2.x:
+
+ - `app.json` to `config.json`
+ - `app.local.json` to `config.local.json`
+ - `app.local.js` to `config.local.js`
+ - etc.
+
+### Data sources
+
+The configuration of data sources remains the same in both 1.x and 2.x
+versions.
+
+### Models
**The 2.x version of loopback-boot no longer creates Models, it's up to the
developer to create them before booting the app.**
@@ -203,7 +117,7 @@ All code samples are referring to the sample project described above.
}
```
-#### Attaching built-in models
+### Attaching built-in models
Models provided by LoopBack, such as `User` or `Role`, are no longer
automatically attached to default data-sources. The data-source configuration
diff --git a/lib/executor.js b/lib/executor.js
index f97f930..ba55438 100644
--- a/lib/executor.js
+++ b/lib/executor.js
@@ -1,6 +1,5 @@
var assert = require('assert');
var _ = require('underscore');
-var loopback = require('loopback');
var semver = require('semver');
var debug = require('debug')('loopback:boot:executor');
@@ -14,6 +13,7 @@ var debug = require('debug')('loopback:boot:executor');
*/
module.exports = function execute(app, instructions) {
+ patchAppLoopback(app);
assertLoopBackVersion(app);
setHost(app, instructions);
@@ -29,18 +29,31 @@ module.exports = function execute(app, instructions) {
enableAnonymousSwagger(app, instructions);
};
+function patchAppLoopback(app) {
+ if (app.loopback) return;
+ // app.loopback was introduced in 1.9.0
+ // patch the app object to make loopback-boot work with older versions too
+ try {
+ app.loopback = require('loopback');
+ } catch(err) {
+ if (err.code === 'MODULE_NOT_FOUND') {
+ console.error(
+ 'When using loopback-boot with loopback <1.9, '+
+ 'the loopback module must be available for `require(\'loopback\')`.');
+ }
+ throw err;
+ }
+}
+
function assertLoopBackVersion(app) {
var RANGE = '1.x || 2.x';
- // app.loopback was introduced in 1.9.0
- var loopback = app.loopback || {};
- var version = loopback.version || '1.0.0';
-
- if (!semver.satisfies(version, RANGE)) {
+ var loopback = app.loopback;
+ if (!semver.satisfies(loopback.version || '1.0.0', RANGE)) {
throw new Error(
'The `app` is powered by an incompatible loopback version %s. ' +
'Supported versions: %s',
- loopback.version || '<1.9',
+ loopback.version || '(unknown)',
RANGE);
}
}