Merge branch 'master' into 2.0

Conflicts:
	README.md
	docs/configuration.md
	lib/executor.js
	package.json

Changes in the docs were merged manually and updated to correctly
describe the 2.x layout.
This commit is contained in:
Miroslav Bajtoš 2014-06-26 14:40:24 +02:00
commit d5cd0a3b50
5 changed files with 398 additions and 190 deletions

359
README.md
View File

@ -4,7 +4,7 @@ LoopBack Boot is a convention-based bootstrapper for LoopBack applications.
**For full documentation, see the official StrongLoop documentation:** **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 ## Installation
@ -23,7 +23,7 @@ app.use(loopback.rest());
app.listen(); 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. complete API reference.
## Versions ## Versions
@ -34,3 +34,358 @@ up to slc version 2.5.
The version range `2.x` supports the new project layout as scaffolded by The version range `2.x` supports the new project layout as scaffolded by
`yo loopback`. `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
<script src="app.bundle.js"></script>
<script>
var app = require('loopback-app');
var User = app.models.User;
User.login(
{ email: 'test@example.com', password: '12345' },
function(err, res) {
if (err) {
console.error('Login failed: ', err);
} else {
console.log('Logged in.');
}
}
);
</script>
```

View File

@ -1,12 +1,12 @@
{ {
"content": [ "content": [
"README.md",
{ {
"title": "Bootstrap API", "title": "API",
"depth": 2 "depth": 2
}, },
"index.js", "index.js",
"browser.js", "browser.js",
"docs/configuration.md", "docs/migrating-from-1x-to-2x.md"
"docs/browserify.md"
] ]
} }

View File

@ -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
<script src="app.bundle.js"></script>
<script>
var app = require('loopback-app');
var User = app.models.User;
User.login({ email: 'test@example.com', password: '12345', function(err, res) {
if (err) {
console.error('Login failed: ', err);
} else {
console.log('Logged in.');
}
});
</script>
```

View File

@ -1,105 +1,4 @@
## Configuration and conventions ## Migrating from 1.x to 2.x
### 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
**Starting point: a sample 1.x project** **Starting point: a sample 1.x project**
@ -136,7 +35,22 @@ var app = loopback();
boot(app, __dirname); boot(app, __dirname);
``` ```
#### Model definitions &amp; 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 **The 2.x version of loopback-boot no longer creates Models, it's up to the
developer to create them before booting the app.** 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 Models provided by LoopBack, such as `User` or `Role`, are no longer
automatically attached to default data-sources. The data-source configuration automatically attached to default data-sources. The data-source configuration

View File

@ -1,6 +1,5 @@
var assert = require('assert'); var assert = require('assert');
var _ = require('underscore'); var _ = require('underscore');
var loopback = require('loopback');
var semver = require('semver'); var semver = require('semver');
var debug = require('debug')('loopback:boot:executor'); var debug = require('debug')('loopback:boot:executor');
@ -14,6 +13,7 @@ var debug = require('debug')('loopback:boot:executor');
*/ */
module.exports = function execute(app, instructions) { module.exports = function execute(app, instructions) {
patchAppLoopback(app);
assertLoopBackVersion(app); assertLoopBackVersion(app);
setHost(app, instructions); setHost(app, instructions);
@ -29,18 +29,31 @@ module.exports = function execute(app, instructions) {
enableAnonymousSwagger(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) { function assertLoopBackVersion(app) {
var RANGE = '1.x || 2.x'; var RANGE = '1.x || 2.x';
// app.loopback was introduced in 1.9.0 var loopback = app.loopback;
var loopback = app.loopback || {}; if (!semver.satisfies(loopback.version || '1.0.0', RANGE)) {
var version = loopback.version || '1.0.0';
if (!semver.satisfies(version, RANGE)) {
throw new Error( throw new Error(
'The `app` is powered by an incompatible loopback version %s. ' + 'The `app` is powered by an incompatible loopback version %s. ' +
'Supported versions: %s', 'Supported versions: %s',
loopback.version || '<1.9', loopback.version || '(unknown)',
RANGE); RANGE);
} }
} }