LoopBack

v1.0.0

LoopBack is part of the StrongLoop Suite.

LoopBack is a mobile backend framework that puts you in control. As a mobile developer you decide where to run your mobile backend - in the cloud or on-premise. LoopBack is built on StrongNode and open source Node.js modules. It leverages the power of Node, the community that stands behind Node, and the support that StrongLoop offers.

LoopBack is here to help you develop mobile applications with rich functionality and data that is in your datacenter and the cloud. Since LoopBack is built on open source, it is highly extensible and familiar with developers already using Node. Node.js leverages Javascript. The ubiquituous language of the web that most developers have already had familiarity with it's ease of use.


Quick Start

Step 1

Install the StrongLoop Suite

Step 2

Setup the StrongLoop Suite Sample App.

$ slc example

The command above takes care of cloning the sample app into a new directory named sls-sample-app and installing all of its dependencies.

Step 3

Run the sample application.

$ cd sls-sample-app
$ slc run app

Step 4

Open the app in a browser.

The sample app should now be running at http://localhost:3000. The homepage of the sample app lists several sample requests you can make against the LoopBack REST API.

View the Sample App

Step 5

Explore the REST API

To browse all the sample app's REST APIs, open the API explorer

View the API Explorer

Next Steps

Check out the rest of the StrongLoop Suite.


Resources

LoopBack was created by StrongLoop. For more information, check out the Node Republic - a community around Node and StrongLoop products.


Concepts

Overview

Before we go into all the wonderful concepts that make up LoopBack, let's first answer a couple of questions:

What is LoopBack?

  • A component in the StrongLoop Suite.
  • A library of Node.js modules for connecting mobile apps to a variety of data sources.
  • A command line tool, slc lb, for generating models and entire applications with the LoopBack library.
  • A set of SDKs for native and web-based mobile clients.

How does LoopBack work?

  • LoopBack applications are made up of three components: Models, Data Sources/Connectors, and the Mobile Clients that consume them.

  • Any mobile or web app can interact with LoopBack through the Model API that are backed by various data sources. The Model API is available locally within Node.js, remotely over REST, and as native mobile SDKs for iOS, Android, and HTML5. Using the API, clients can query databases, store data, upload files, send emails, create push notifications, register users, and any other behavior provided by data sources.

concepts

Mobile Clients

PLACEHOLDER FOR SDK INTRO

In summary, mobile clients work with objects that are connected to LoopBack to provide data and perform actions.Those objects are captured as models in LoopBack.

Models

What is a Model?

A LoopBack Model consists of the following:

  • Application data
  • Validation rules
  • Data access capabilities
  • Business logic

A mobile client uses the remote API provided by Models to request any information needed to display a useful interface to the user or trigger actions on the models to interact with backend systems.

Let's use a simple example to explain what a model can do for you.

Defining a model

For example, an e-commerce app might have Product and Inventory Models. A mobile client could use the Product Model API to search through all of the Products in a database. A client could join the Product and Inventory data to determine what products are in stock, or the Product Model could provide a server-side function (or remote method) that aggregates this information.

// Step 1: Create Models
var Model = require('loopback').Model;
var Product = Model.extend('product');
var Inventory = Model.extend('customer');

NOTE: Models are schema-less by default, but some data sources, such as relational databases, require schemas. Additionally, schemas are immensely valuable for establishing the common knowledge of business data so that the data exchange can be agreed and documented while data coming from mobile clients can be validated and/or sanitized . See Sanitizing and Validating Models if your application needs to connect to an RDBMS, for example.

Attaching to Data Sources

Instances of a Model carry application data. But they are not very interesting until applications can create, retrieve, update, or delete (CRUD) model instances. LoopBack introduces the DataSource concept to provide the data access capabilities to models. Attaching a Model to a DataSource gives you access to a powerful API mixed into Models by the Connector behind a DataSource. The MongoDB Connector, for example, mixes in a create method that allows us to store a new Product in the database:

// Step 2: Attach Data Sources
var db = loopback.createDataSource({
  connector: require('loopback-connector-mongodb')
});

// Enables the Model to use the MongoDB API
Product.attachTo(db);

// Create a new product in the database
Product.create({ name: 'widget', price: 99.99 }, function(err, widget) {
  console.log(widget.id); // The product's id, added by MongoDB
});

Now the models have both data and behaviors. How can the mobile clients benefit from them? We need a way to make the models available from mobile clients.

Exposing to Mobile Clients

Models can be exposed to mobile clients using one of the remoting middlewares. This example uses the app.rest middleware to expose the Product Model's API over REST.

// Step 3: Create a LoopBack Application
var app = loopback();

// Use the REST remoting middleware
app.use(loopback.rest());

// Expose the `Product` model
app.model(Product);

After this, you'll have the Product model with CRUD functions working remotely from the mobile clients. Please note the model is schema-less till now and the data are not checked.

Sanitizing and Validating Models

A Model can be described in plain json or JavaScript. The description is called schema. Once a schema is defined for a Model, it will validate and sanitize data before giving it to a Data Source. For example, the Product Model has a schema that will not change. The example below updates the Product Model with a schema written in LoopBack Definition Language, a well-documented flavor of JSON.

// Step 4: Add a Schema
var productSchema = {
  "name": { "type": "string", "required": true },
  "price": "number"
};
var Product = Model.extend('product', productSchema);

On one hand, If a remote client tries to save a product with extra properties (e.g. description), those properties will be removed before saving the Model. On the other hand, the Model will only be saved if the product contains the required name property.

More About Models

Data Sources and Connectors

Now you see the power of LoopBack models. A model gets rich set of functions out of the box with the contribution from Data Sources and Connectors.

The concept of DataSource is introduced to encapsulate business logic to exchange data between models and various data sources. Data sources are typically databases that provide create, retrieve, update, and delete (CRUD) functions. LoopBack also generalize other backend services, such as REST APIs, SOAP Web Services, and Storage Services, as data sources.

LoopBack allows you to connect to many sources of data and services both in the cloud and on-premise in your data center. DataSources are accessed through a plugin called a Connector in LoopBack. Plugins are highly customizable and extensible. Unlike other mobile backend, LoopBack can leverage your existing data and organize them in the form of models.

Connectors implement the data exchange logic using database drivers or other client APIs. In general, connectors are not used directly by application code. The DataSource class provides APIs to configure the underlying connector and exposes functions via DataSource or model classes.

LoopBack Connector Modules

Type Package Name
Memory Built-in
MongoDB loopback-connector-mongodb
Oracle loopback-connector-oracle
REST loopback-connector-rest

For more information, please read the LoopBack DataSource and Connector Guide.

REST

Functions defined in LoopBack Models can be made available as a REST endpoint. You can see and experiment with your REST api using the LoopBack API Explorer.

LoopBack also supports other protocols for your API as well. Socket.io is another protocol that is currently being developed.

For more information, please read Model REST APIs.

Remoting

With LoopBack you can add whatever functionality you like either by yourself or leveraging functionality from other open source modules from the community. The ability to "mix in" behaviors are available through the inherent power of Javascript's less resrictive inheritance model.

LoopBack takes this one step further by allowing you to seamlessly invoke server side code running in LoopBack in the backend from the your client on the front end.

For more information, please read the Remoting Guide.


Command Line

The StrongLoop Suite comes bundled with a command line tool called StrongLoop Command or slc. StrongLoop Command allows you to create boilerplate for LoopBack and other StrongNode applications.

Commands

slc lb provides the following commands.

workspace

Initialize a workspace as a new empty directory with an optional name. The default name is "loopback-workspace".

$ slc lb workspace my-loopback-workspace

project

Create a LoopBack application in a new directory within the current directory using the given name. The name arg is required.

$ cd my-loopback-workspace
$ slc lb project my-app
$ slc run my-app

model

Create a model in an existing LoopBack application. If you provide the -i or --interactive flags, you will be prompted through a model configuration. The --data-source flag allows you to specify the name of a custom data. Otheriwse it will use the data source named "db".

$ cd my-app
$ slc lb model product

Node.js API

App

Create a Loopback application.

var loopback = require('loopback');
var app = loopback();

app.get('/', function(req, res){
  res.send('hello world');
});

app.listen(3000);

Notes

app.model(Model)

Expose a Model to remote clients.

// create a testing data source
var memory = loopback.memory();
var Color = memory.createModel('color', {name: String});

app.model(Color);
app.use(loopback.rest());

Note - this will expose all shared methods on the model.

app.models()

Get the app's exposed models.

var models = app.models();

models.forEach(function (Model) {
  console.log(Model.modelName); // color
});

app.docs(options)

Enable swagger REST api documentation.

Options

Example

// enable docs
app.docs({basePath: 'http://localhost:3000'});

Run your app then navigate to the api explorer. Enter your API basepath to view your generated docs.

Model

A Loopback Model is a vanilla JavaScript class constructor with an attached set of properties and options. A Model instance is created by passing a data object containing properties to the Model constructor. A Model constructor will clean the object passed to it and only set the values matching the properties you define.

// valid color
var Color = loopback.createModel('color', {name: String});
var red = new Color({name: 'red'});
console.log(red.name); // red

// invalid color
var foo = new Color({bar: 'bat baz'});
console.log(foo.bar); // undefined

Properties

A model defines a list of property names, types and other validation metadata. A DataSource uses this definition to validate a Model during operations such as save().

Options

Some DataSources may support additional Model options.

Define A Loopbackmodel.

var User = loopback.createModel('user', {
  first: String,
  last: String,
  age: Number
});

Validation (expiremental)

Model.validatesFormatOf(property, options)

Require a model to include a property that matches the given format.

User.validatesFormat('name', {with: /\w+/});
Model.validatesPresenceOf(properties...)

Require a model to include a property to be considered valid.

User.validatesPresenceOf('first', 'last', 'age');
Model.validatesLengthOf(property, options)

Require a property length to be within a specified range.

User.validatesLengthOf('password', {min: 5, message: {min: 'Password is too short'}});
Model.validatesInclusionOf(property, options)

Require a value for property to be in the specified array.

User.validatesInclusionOf('gender', {in: ['male', 'female']});
Model.validatesExclusionOf(property, options)

Require a value for property to not exist in the specified array.

User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']});
Model.validatesNumericalityOf(property, options)

Require a value for property to be a specific type of Number.

User.validatesNumericalityOf('age', {int: true});
Model.validatesUniquenessOf(property, options)

Ensure the value for property is unique in the collection of models.

User.validatesUniquenessOf('email', {message: 'email is not unique'});

Note: not available for all connectors.

Currently supported in these connectors:

myModel.isValid()

Validate the model instance.

user.isValid(function (valid) {
    if (!valid) {
        console.log(user.errors);
        // => hash of errors
        // => {
        // =>   username: [errmessage, errmessage, ...],
        // =>   email: ...
        // => }    
    }
});

Model.properties

An object containing a normalized set of properties supplied to loopback.createModel(name, properties).

Example:

var props = {
  a: String,
  b: {type: 'Number'},
  c: {type: 'String', min: 10, max: 100},
  d: Date,
  e: loopback.GeoPoint
};

var MyModel = loopback.createModel('foo', props);

console.log(MyModel.properties);

Outputs:

{
  "a": {type: String},
  "b": {type: Number},
  "c": {
    "type": String,
    "min": 10,
    "max": 100
  },
  "d": {type: Date},
  "e": {type: GeoPoint},
  "id": {
    "id": 1
  }
}

Model.attachTo(dataSource)

Attach a model to a DataSource. Attaching a DataSource updates the model with additional methods and behaviors.

var oracle = loopback.createDataSource({
  connector: require('loopback-connector-oracle'),
  host: '111.22.333.44',
  database: 'MYDB',
  username: 'username',
  password: 'password'
});

User.attachTo(oracle);

Note: until a model is attached to a data source it will not have any attached methods.

CRUD and Query Mixins

Mixins are added by attaching a vanilla model to a data source with a connector. Each connector enables its own set of operations that are mixed into a Model as methods. To see available methods for a data source call dataSource.operations().

Log the available methods for a memory data source.

var ops = loopback
    .createDataSource({connector: loopback.Memory})
    .operations();

console.log(Object.keys(ops));

Outputs:

[ 'create',
  'updateOrCreate',
  'upsert',
  'findOrCreate',
  'exists',
  'findById',
  'find',
  'all',
  'findOne',
  'destroyAll',
  'deleteAll',
  'count',
  'include',
  'relationNameFor',
  'hasMany',
  'belongsTo',
  'hasAndBelongsToMany',
  'save',
  'isNewRecord',
  'destroy',
  'delete',
  'updateAttribute',
  'updateAttributes',
  'reload' ]

Here is the definition of the count() operation.

{
  accepts: [ { arg: 'where', type: 'object' } ],
  http: { verb: 'get', path: '/count' },
  remoteEnabled: true,
  name: 'count'
}

Static Methods

Note: These are the default mixin methods for a Model attached to a data source. See the specific connector for additional API documentation.

Model.create(data, [callback])

Create an instance of Model with given data and save to the attached data source. Callback is optional.

User.create({first: 'Joe', last: 'Bob'}, function(err, user) {
  console.log(user instanceof User); // true
});

Note: You must include a callback and use the created model provided in the callback if your code depends on your model being saved or having an id.

Model.count([query], callback)

Query count of Model instances in data source. Optional query param allows to count filtered set of Model instances.

User.count({approved: true}, function(err, count) {
  console.log(count); // 2081
});
Model.find(filter, callback)

Find all instances of Model, matched by query. Fields used for filter and sort should be declared with {index: true} in model definition.

filter

  • where Object { key: val, key2: {gt: 'val2'}} The search criteria

    • Format: {key: val} or {key: {op: val}}
    • Operations:
      • gt: >
      • gte: >=
      • lt: <
      • lte: <=
      • between
      • inq: IN
      • nin: NOT IN
      • neq: !=
      • like: LIKE
      • nlike: NOT LIKE
  • include String, Object or Array Allows you to load relations of several objects and optimize numbers of requests.

    • Format:
      • 'posts': Load posts
      • ['posts', 'passports']: Load posts and passports
      • {'owner': 'posts'}: Load owner and owner's posts
      • {'owner': ['posts', 'passports']}: Load owner, owner's posts, and owner's passports
      • {'owner': [{posts: 'images'}, 'passports']}: Load owner, owner's posts, owner's posts' images, and owner's passports
  • order String The sorting order

    • Format: 'key1 ASC, key2 DESC'
  • limit Number The maximum number of instances to be returned

  • skip Number Skip the number of instances
  • offset Number Alias for skip

  • fields Object|Array|String The included/excluded fields

    • ['foo'] or 'foo' - include only the foo property
    • ['foo', 'bar'] - include the foo and bar properties
    • {foo: true} - include only foo
    • {bat: false} - include all properties, exclude bat

Find the second page of 10 users over age 21 in descending order exluding the password property.

User.find({
  where: {
    age: {gt: 21}},
    order: 'age DESC',
    limit: 10,
    skip: 10,
    fields: {password: false}
  },
  console.log
);

Note: See the specific connector's docs for more info.

Model.destroyAll(callback)

Delete all Model instances from data source. Note: destroyAll method does not perform destroy hooks.

Model.findById(id, callback)

Find instance by id.

User.findById(23, function(err, user) {
  console.info(user.id); // 23
});
Model.findOne(where, callback)

Find a single instance that matches the given where expression.

User.findOne({id: 23}, function(err, user) {
  console.info(user.id); // 23
});
Model.upsert(data, callback)

Update when record with id=data.id found, insert otherwise. Note: no setters, validations or hooks applied when using upsert.

Custom Static Methods

Define a static model method.

User.login = function (username, password, fn) {
  var passwordHash = hashPassword(password);
  this.findOne({username: username}, function (err, user) {
    var failErr = new Error('login failed');

    if(err) {
      fn(err);
    } else if(!user) {
      fn(failErr);
    } else if(user.password === passwordHash) {
      MySessionModel.create({userId: user.id}, function (err, session) {
        fn(null, session.id);
      });
    } else {
      fn(failErr);
    }
  });
}

Setup the static model method to be exposed to clients as a remote method.

loopback.remoteMethod(
  User.login,
  {
    accepts: [
      {arg: 'username', type: 'string', required: true},
      {arg: 'password', type: 'string', required: true}
    ],
    returns: {arg: 'sessionId', type: 'any'},
    http: {path: '/sign-in'}
  }
);

Instance Methods

Note: These are the default mixin methods for a Model attached to a data source. See the specific connector for additional API documentation.

model.save([options], [callback])

Save an instance of a Model to the attached data source.

var joe = new User({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) {
  if(user.errors) {
    console.log(user.errors);
  } else {
    console.log(user.id);
  }
});
model.updateAttributes(data, [callback])

Save specified attributes to the attached data source.

user.updateAttributes({
  first: 'updatedFirst',
  name: 'updatedLast'
}, fn);
model.destroy([callback])

Remove a model from the attached data source.

model.destroy(function(err) {
  // model instance destroyed
});
Custom Instance Methods

Define an instance method.

User.prototype.logout = function (fn) {
  MySessionModel.destroyAll({userId: this.id}, fn);
}

Define a remote model instance method.

loopback.remoteMethod(User.prototype.logout)

Remote Methods

Both instance and static methods can be exposed to clients. A remote method must accept a callback with the conventional fn(err, result, ...) signature.

loopback.remoteMethod(fn, [options])

Expose a remote method.

Product.stats = function(fn) {
  var calc = require('./stats');

  Product.find(function(err, products) {
    var productStats = calc(products);
    fn(null, productStats);
  });
}

loopback.remoteMethod(
  Product.stats,
  {
    returns: {arg: 'stats', type: 'object'},
    http: {path: '/info', verb: 'get'}
  }
);

Options

  • accepts - (optional) an arguments description specifying the remote method's arguments.
  • returns - (optional) an arguments description specifying the remote methods callback arguments.
  • http - (advanced / optional, object) http routing info
    • http.path - the path relative to the model the method will be exposed at. May be a path fragment (eg. '/:myArg') which will be populated by an arg of the same name in the accepts description. For example the stats method above will be at the whole path /products/stats.
    • http.verb - (get, post, put, del, all) - the route verb the method will be available from.

Argument Description

An arguments description defines either a single argument as an object or an ordered set of arguments as an array.

// examples
{arg: 'myArg', type: 'number'}

[
  {arg: 'arg1', type: 'number', required: true},
  {arg: 'arg2', type: 'array'}
]

Types

Each argument may define any of the loopback types.

Notes:

  • The callback is an assumed argument and does not need to be specified in the accepts array.
  • The err argument is also assumed and does not need to be specified in the returns array.

Remote Hooks

Run a function before or after a remote method is called by a client.

// *.save === prototype.save
User.beforeRemote('*.save', function(ctx, user, next) {
  if(ctx.user) {
    next();
  } else {
    next(new Error('must be logged in to update'))
  }
});

User.afterRemote('*.save', function(ctx, user, next) {
  console.log('user has been saved', user);
  next();
});

Remote hooks also support wildcards. Run a function before any remote method is called.

// ** will match both prototype.* and *.*
User.beforeRemote('**', function(ctx, user, next) {
  console.log(ctx.methodString, 'was invoked remotely'); // users.prototype.save was invoked remotely
  next();
});

Other wildcard examples

// run before any static method eg. User.find
User.beforeRemote('*', ...);

// run before any instance method eg. User.prototype.save
User.beforeRemote('prototype.*', ...);

// prevent password hashes from being sent to clients
User.afterRemote('**', function (ctx, user, next) {
  if(ctx.result) {
    if(Array.isArray(ctx.result)) {
      ctx.result.forEach(function (result) {
        result.password = undefined;
      });
    } else {
      ctx.result.password = undefined;
    }
  }

  next();
});

Context

Remote hooks are provided with a Context ctx object which contains transport specific data (eg. for http: req and res). The ctx object also has a set of consistent apis across transports.

ctx.user

A Model representing the user calling the method remotely. Note: this is undefined if the remote method is not invoked by a logged in user.

ctx.result

During afterRemote hooks, ctx.result will contain the data about to be sent to a client. Modify this object to transform data before it is sent.

Rest

When loopback.rest is used the following ctx properties are available.

ctx.req

The express ServerRequest object. See full documentation.

ctx.res

The express ServerResponse object. See full documentation.

Relationships

Model.hasMany(Model)

Define a "one to many" relationship.

// by referencing model
Book.hasMany(Chapter);
// specify the name
Book.hasMany('chapters', {model: Chapter});

Query and create the related models.

Book.create(function(err, book) {
  // create a chapter instance
  // ready to be saved in the data source
  var chapter = book.chapters.build({name: 'Chapter 1'});

  // save the new chapter
  chapter.save();

  // you can also call the Chapter.create method with
  // the `chapters` property which will build a chapter
  // instance and save the it in the data source
  book.chapters.create({name: 'Chapter 2'}, function(err, savedChapter) {
    // this callback is optional
  });

  // query chapters for the book using the 
  book.chapters(function(err, chapters) {
    // all chapters with bookId = book.id
    console.log(chapters);
  });

  book.chapters({where: {name: 'test'}, function(err, chapters) {
    // all chapters with bookId = book.id and name = 'test'
    console.log(chapters);
  });
});

Shared Methods

Any static or instance method can be decorated as shared. These methods are exposed over the provided transport (eg. loopback.rest).

Data Source

A Loopback DataSource provides Models with the ability to manipulate data. Attaching a DataSource to a Model adds instance methods and static methods to the Model. The added methods may be remote methods.

Define a data source for persisting models.

var oracle = loopback.createDataSource({
  connector: 'oracle',
  host: '111.22.333.44',
  database: 'MYDB',
  username: 'username',
  password: 'password'
});

dataSource.createModel(name, properties, options)

Define a model and attach it to a DataSource.

var Color = oracle.createModel('color', {name: String});

dataSource.discoverModelDefinitions([username], fn)

Discover a set of model definitions (table or collection names) based on tables or collections in a data source.

oracle.discoverModelDefinitions(function (err, models) {
  models.forEach(function (def) {
    // def.name ~ the model name
    oracle.discoverSchema(null, def.name, function (err, schema) {
      console.log(schema);
    });
  });
});

dataSource.discoverSchema([owner], name, fn)

Discover the schema of a specific table or collection.

Example schema from oracle connector:

    {
      "name": "Product",
      "options": {
        "idInjection": false,
        "oracle": {
          "schema": "BLACKPOOL",
          "table": "PRODUCT"
        }
      },
      "properties": {
        "id": {
          "type": "String",
          "required": true,
          "length": 20,
          "id": 1,
          "oracle": {
            "columnName": "ID",
            "dataType": "VARCHAR2",
            "dataLength": 20,
            "nullable": "N"
          }
        },
        "name": {
          "type": "String",
          "required": false,
          "length": 64,
          "oracle": {
            "columnName": "NAME",
            "dataType": "VARCHAR2",
            "dataLength": 64,
            "nullable": "Y"
          }
        },
        "audibleRange": {
          "type": "Number",
          "required": false,
          "length": 22,
          "oracle": {
            "columnName": "AUDIBLE_RANGE",
            "dataType": "NUMBER",
            "dataLength": 22,
            "nullable": "Y"
          }
        },
        "effectiveRange": {
          "type": "Number",
          "required": false,
          "length": 22,
          "oracle": {
            "columnName": "EFFECTIVE_RANGE",
            "dataType": "NUMBER",
            "dataLength": 22,
            "nullable": "Y"
          }
        },
        "rounds": {
          "type": "Number",
          "required": false,
          "length": 22,
          "oracle": {
            "columnName": "ROUNDS",
            "dataType": "NUMBER",
            "dataLength": 22,
            "nullable": "Y"
          }
        },
        "extras": {
          "type": "String",
          "required": false,
          "length": 64,
          "oracle": {
            "columnName": "EXTRAS",
            "dataType": "VARCHAR2",
            "dataLength": 64,
            "nullable": "Y"
          }
        },
        "fireModes": {
          "type": "String",
          "required": false,
          "length": 64,
          "oracle": {
            "columnName": "FIRE_MODES",
            "dataType": "VARCHAR2",
            "dataLength": 64,
            "nullable": "Y"
          }
        }
      }
    }

dataSource.enableRemote(operation)

Enable remote access to a data source operation. Each connector has its own set of set remotely enabled and disabled operations. You can always list these by calling dataSource.operations().

dataSource.disableRemote(operation)

Disable remote access to a data source operation. Each connector has its own set of set enabled and disabled operations. You can always list these by calling dataSource.operations().

// all rest data source operations are
// disabled by default
var oracle = loopback.createDataSource({
  connector: require('loopback-connector-oracle'),
  host: '...',
  ...
});

// or only disable it as a remote method
oracle.disableRemote('destroyAll');

Notes:

  • disabled operations will not be added to attached models
  • disabling the remoting for a method only affects client access (it will still be available from server models)
  • data sources must enable / disable operations before attaching or creating models

dataSource.operations()

List the enabled and disabled operations.

console.log(oracle.operations());

Output:

{
  find: {
    remoteEnabled: true,
    accepts: [...],
    returns: [...]
    enabled: true
  },
  save: {
    remoteEnabled: true,
    prototype: true,
    accepts: [...],
    returns: [...],
    enabled: true
  },
  ...
}

Connectors

Create a data source with a specific connector. See available connectors for specific connector documentation.

var memory = loopback.createDataSource({
  connector: loopback.Memory
});

Database Connectors

Other Connectors

Installing Connectors

Include the connector in your package.json dependencies and run npm install.

{
  "dependencies": {
    "loopback-connector-oracle": "latest"
  }
}
Memory Connector

The built-in memory connector allows you to test your application without connecting to an actual persistent data source, such as a database. Although the memory connector is very well tested it is not recommended to be used in production. Creating a data source using the memory connector is very simple.

// use the built in memory function
// to create a memory data source
var memory = loopback.memory();

// or create it using the standard
// data source creation api
var memory = loopback.createDataSource({
  connector: loopback.Memory
});

// create a model using the
// memory data source
var properties = {
  name: String,
  price: Number
};

var Product = memory.createModel('product', properties);

Product.create([
  {name: 'apple', price: 0.79},
  {name: 'pear', price: 1.29},
  {name: 'orange', price: 0.59},
], count);

function count() {
  Product.count(console.log); // 3
}

CRUD / Query

The memory connector supports all the standard query and crud operations to allow you to test your models against an in memory data source.

GeoPoint Filtering

The memory connector also supports geo-filtering when using the find() operation with an attached model. See GeoPoint for more information on geo-filtering.

GeoPoint

Use the GeoPoint class.

var GeoPoint = require('loopback').GeoPoint;

Embed a latitude / longitude point in a Model.

var CoffeeShop = loopback.createModel('coffee-shop', {
  location: 'GeoPoint'
});

Loopback Model's with a GeoPoint property and an attached DataSource may be queried using geo spatial filters and sorting.

Find the 3 nearest coffee shops.

CoffeeShop.attachTo(oracle);
var here = new GeoPoint({lat: 10.32424, lng: 5.84978});
CoffeeShop.find({where: {location: {near: here}}, limit:3}, function(err, nearbyShops) {
  console.info(nearbyShops); // [CoffeeShop, ...]
});

geoPoint.distanceTo(geoPoint, options)

Get the distance to another GeoPoint.

var here = new GeoPoint({lat: 10, lng: 10});
var there = new GeoPoint({lat: 5, lng: 5});
console.log(here.distanceTo(there, {type: 'miles'})); // 438

GeoPoint.distanceBetween(a, b, options)

Get the distance between two points.

GeoPoint.distanceBetween(here, there, {type: 'miles'}) // 438

Distance Types

Note: all distance methods use miles by default.

  • miles
  • radians
  • kilometers
  • meters
  • miles
  • feet
  • degrees

geoPoint.lat

The latitude point in degrees. Range: -90 to 90.

geoPoint.lng

The longitude point in degrees. Range: -180 to 180.

Loopback Types

Various APIs in Loopback accept type descriptions (eg. remote methods, loopback.createModel()). The following is a list of supported types.

  • null - JSON null
  • Boolean - JSON boolean
  • Number - JSON number
  • String - JSON string
  • Object - JSON object
  • Array - JSON array
  • Date - a JavaScript date object
  • Buffer - a node.js Buffer object
  • GeoPoint - A Loopback GeoPoint object.

Bundled Models

The Loopback library is unopinioned in the way you define your app's data and logic. Loopback also bundles useful pre-built models for common use cases.

  • User - register and authenticate users of your app locally or against 3rd party services.
  • Email - send emails to your app users using smtp or 3rd party services.

Defining a model with loopback.createModel() is really just extending the base loopback.Model type using loopback.Model.extend(). The bundled models extend from the base loopback.Model allowing you to extend them arbitrarily.

User Model

Register and authenticate users of your app locally or against 3rd party services.

Define a User Model

Extend a vanilla Loopback model using the built in User model.

// create a data source
var memory = loopback.memory();

// define a User model
var User = loopback.User.extend('user');

// attach to the memory connector
User.attachTo(memory);

// also attach the session model to a data source
User.session.attachTo(memory);

// expose over the app's api
app.model(User);

Note: By default the loopback.User model uses the loopback.Session model to persist sessions. You can change this by setting the session property.

Note: You must attach both the User and User.session model's to a data source!

User Creation

Create a user like any other model.

// username and password are not required
User.create({email: 'foo@bar.com', password: 'bar'}, function(err, user) {
  console.log(user);
});
Login a User

Create a session for a user using the local auth strategy.

Node.js

User.login({username: 'foo', password: 'bar'}, function(err, session) {
  console.log(session);
});

REST

You must provide a username and password over rest. To ensure these values are encrypted, include these as part of the body and make sure you are serving your app over https (through a proxy or using the https node server).

POST

  /users/login
  ...
  {
    "email": "foo@bar.com",
    "password": "bar"
  }

  ...

  200 OK
  {
    "sid": "1234abcdefg",
    "uid": "123"
  }
Logout a User

Node.js

// login a user and logout
User.login({"email": "foo@bar.com", "password": "bar"}, function(err, session) {
  User.logout(session.id, function(err) {
    // user logged out
  });
});

// logout a user (server side only)
User.findOne({email: 'foo@bar.com'}, function(err, user) {
  user.logout();
});

REST

POST /users/logout
...
{
  "sid": "<session id from user login>"
}
Verify Email Addresses

Require a user to verify their email address before being able to login. This will send an email to the user containing a link to verify their address. Once the user follows the link they will be redirected to / and be able to login normally.

User.requireEmailVerfication = true;
User.afterRemote('create', function(ctx, user, next) {
  var options = {
    type: 'email',
    to: user.email,
    from: 'noreply@myapp.com',
    subject: 'Thanks for Registering at FooBar',
    text: 'Please verify your email address!'
    template: 'verify.ejs',
    redirect: '/'
  };

  user.verify(options, next);
});
Send Reset Password Email

Send an email to the user's supplied email address containing a link to reset their password.

User.reset(email, function(err) {
  console.log('email sent');
});
Remote Password Reset

The password reset email will send users to a page rendered by loopback with fields required to reset the user's password. You may customize this template by defining a resetTemplate setting.

User.settings.resetTemplate = 'reset.ejs';
Remote Password Reset Confirmation

Confirm the password reset.

User.confirmReset(token, function(err) {
  console.log(err || 'your password was reset');
});

Session Model

Identify users by creating sessions when they connect to your loopback app. By default the loopback.User model uses the loopback.Session model to persist sessions. You can change this by setting the session property.

// define a custom session model    
var MySession = loopback.Session.extend('my-session');

// define a custom User model
var User = loopback.User.extend('user');

// use the custom session model
User.session = MySession;

// attach both Session and User to a data source
User.attachTo(loopback.memory());
MySession.attachTo(loopback.memory());

Email Model

Send emails from your loopback app.

REST Router

Expose models over rest using the loopback.rest router.

app.use(loopback.rest());

REST Documentation

View generated REST documentation by visiting: http://localhost:3000/_docs.

SocketIO Middleware (Not Available)

Coming Soon - Expose models over socket.io using the loopback.sio() middleware.

app.use(loopback.sio);

iOS API

Stay tuned. Currently in development.

Browser API

Stay tuned. Currently in development.

Android API

Stay tuned. Currently in development.

REST API

The REST API allows clients to interact with the LoopBack models using HTTP. The clients can be a web browser, a JavaScript program, a mobile SDK, a curl script, or anything that can act as an HTTP client.

LoopBack automatically binds a model to a list of HTTP endpoints that provide REST APIs for model instance data manipulations (CRUD) and other remote operations.

We'll use a simple model called Location (locations for rental) to illustrate what REST APIs are exposed by LoopBack.

By default, the REST APIs are mounted to /<pluralFormOfTheModelName>, for example, /locations, to the base URL such as http://localhost:3000/.

CRUD remote methods

For a model backed by a data source that supports CRUD operations, you'll see the following endpoints:

  • Model.create: POST /locations
  • Model.upsert: PUT /locations
  • Model.exists: GET /locations/:id/exists
  • Model.findById: GET /locations/:id
  • Model.find: GET /locations
  • Model.findOne: GET /locations/findOne
  • Model.deleteById: DELETE /locations/:id
  • Model.count: GET /locations/count
  • Model.prototype.updateAttributes: PUT /locations/:id

Custom remote methods

To expose a JavaScript method as REST API, we can simply describe the method as follows:

loopback.remoteMethod(
  Location.nearby,
  {
    description: 'Find nearby locations around the geo point',
    accepts: [
      {arg: 'here', type: 'GeoPoint', required: true, description: 'geo location (lat & lng)'},
      {arg: 'page', type: 'Number', description: 'number of pages (page size=10)'},
      {arg: 'max', type: 'Number', description: 'max distance in miles'}
    ],
    returns: {arg: 'locations', root: true},
    http: {verb: 'POST', path: '/nearby'}
  }
);

The remoting is defined using the following properties:

  • description: Description of the REST API
  • accepts: An array of parameters, each parameter has a name, a type, and an optional description
  • returns: Description of the return value
  • http: Binding to the HTTP endpoint, including the verb and path

Request Format

For POST and PUT requests, the request body must be JSON, with the Content-Type header set to application/json.

Encode the JSON object as query string

LoopBack uses the syntax from node-querystring to encode JSON objects or arrays as query string. For example,

user[name][first]=John&user[email]=callback@strongloop.com
==>
{ user: { name: { first: 'John' }, email: 'callback@strongloop.com' } }

user[names][]=John&user[names][]=Mary&user[email]=callback@strongloop.com
==>
{ user: { names: ['John', 'Mary'], email: 'callback@strongloop.com' }}

items=a&items=b
==> { items: ['a', 'b'] }

For more examples, please check out node-querystring

Response Format

The response format for all requests is a JSON object or array if present. Some responses have an empty body.

Whether a request succeeded is indicated by the HTTP status code. A 2xx status code indicates success, whereas a 4xx status code indicates request related issues. 5xx status code reports server side problems.

The response for an error is in the following format:

{
"error": {
    "message": "could not find a model with id 1",
    "stack": "Error: could not find a model with id 1\n ...",
    "statusCode": 404
    }
}

Generated APIs

create

Create a new instance of the model and persist it into the data source

Definition

POST /locations

Arguments

  • data The model instance data

Example Request

curl -X POST -H "Content-Type:application/json" \
-d '{"name": "L1", "street": "107 S B St", "city": "San Mateo", "zipcode": "94401"}' \
http://localhost:3000/locations

Example Response

{
  "id": "96",
  "street": "107 S B St",
  "city": "San Mateo",
  "zipcode": 94401,
  "name": "L1",
  "geo": {
    "lat": 37.5670042,
    "lng": -122.3240212
  }
}

Potential Errors

  • None

upsert

Update an existing model instance or insert a new one into the data source

Definition

PUT /locations

Arguments

  • data The model instance data

Example Request

Insert
curl -X PUT -H "Content-Type:application/json" \
-d '{"name": "L1", "street": "107 S B St", "city": "San Mateo", "zipcode": "94401"}' \
http://localhost:3000/locations
Update
curl -X PUT -H "Content-Type:applicatin/json" \
-d '{"id": "98", "name": "L4", "street": "107 S B St", "city": "San Mateo", \
"zipcode": "94401"}' http://localhost:3000/locations

Example Response

Insert
{
  "id": "98",
  "street": "107 S B St",
  "city": "San Mateo",
  "zipcode": 94401,
  "name": "L1",
  "geo": {
    "lat": 37.5670042,
    "lng": -122.3240212
  }
}
Update
{
  "id": "98",
  "street": "107 S B St",
  "city": "San Mateo",
  "zipcode": 94401,
  "name": "L4"
}

Potential Errors

  • None

exists

Check whether a model instance exists by id in the data source

Definition

GET /locations/exists

Arguments

  • id The model id

Example Request

curl http://localhost:3000/locations/88/exists

Example Response

{
    "exists": true
}

Potential Errors

  • None

findById

Find a model instance by id from the data source

Definition

GET /locations/{id}

Arguments

  • id The model id

Example Request

curl http://localhost:3000/locations/88

Example Response

{
    "id": "88",
    "street": "390 Lang Road",
    "city": "Burlingame",
    "zipcode": 94010,
    "name": "Bay Area Firearms",
    "geo": {
        "lat": 37.5874391,
        "lng": -122.3381437
    }
}

Potential Errors

  • None

find

Find all instances of the model matched by filter from the data source

Definition

GET /locations

Arguments

  • filter The filter that defines where, order, fields, skip, and limit

    • where Object { key: val, key2: {gt: 'val2'}} The search criteria

      • Format: {key: val} or {key: {op: val}}
      • Operations:
        • gt: >
        • gte: >=
        • lt: <
        • lte: <=
        • between
        • inq: IN
        • nin: NOT IN
        • neq: !=
        • like: LIKE
        • nlike: NOT LIKE
    • include String, Object or Array Allows you to load relations of several objects and optimize numbers of requests.

      • Format:
        • 'posts': Load posts
        • ['posts', 'passports']: Load posts and passports
        • {'owner': 'posts'}: Load owner and owner's posts
        • {'owner': ['posts', 'passports']}: Load owner, owner's posts, and owner's passports
        • {'owner': [{posts: 'images'}, 'passports']}: Load owner, owner's posts, owner's posts' images, and owner's passports
    • order String The sorting order

      • Format: 'key1 ASC, key2 DESC'
    • limit Number The maximum number of instances to be returned

    • skip Number Skip the number of instances
    • offset Number Alias for skip

    • fields Object|Array|String The included/excluded fields

    • ['foo'] or 'foo' - include only the foo property
    • ['foo', 'bar'] - include the foo and bar properties
    • {foo: true} - include only foo
    • {bat: false} - include all properties, exclude bat

For example,

  • '/weapons': Weapons
  • '/weapons?filter[limit]=2&filter[offset]=5': Paginated Weapons
  • '/weapons?filter[where][name]=M1911': Weapons with name M1911
  • '/weapons?filter[where][audibleRange][lt]=10': Weapons with audioRange < 10
  • '/weapons?filter[fields][name]=1&filter[fields][effectiveRange]=1': Only name and effective ranges
  • '/weapons?filter[where][effectiveRange][gt]=900&filter[limit]=3': The top 3 weapons with a range over 900 meters
  • '/weapons?filter[order]=audibleRange%20DESC&filter[limit]=3': The loudest 3 weapons

  • '/locations': Locations

  • '/locations?filter[where][geo][near]=153.536,-28.1&filter[limit]=3': The 3 closest locations to a given geo point

Example Request

Find without filter
curl http://localhost:3000/locations
Find with a filter
curl http://localhost:3000/locations?filter%5Blimit%5D=2

Note: For curl, [ needs to be encoded as %5B, and ] as %5D.

Example Response

[
  {
    "id": "87",
    "street": "7153 East Thomas Road",
    "city": "Scottsdale",
    "zipcode": 85251,
    "name": "Phoenix Equipment Rentals",
    "geo": {
      "lat": 33.48034450000001,
      "lng": -111.9271738
    }
  },
  {
    "id": "88",
    "street": "390 Lang Road",
    "city": "Burlingame",
    "zipcode": 94010,
    "name": "Bay Area Firearms",
    "geo": {
      "lat": 37.5874391,
      "lng": -122.3381437
    }
  }
]

Potential Errors

  • None

findOne

Find first instance of the model matched by filter from the data source

Definition

GET /locations/findOne

Arguments

  • filter The filter that defines where, order, fields, skip, and limit. It's same as find's filter argument. Please see find for more details.

Example Request

curl http://localhost:3000/locations/findOne?filter%5Bwhere%5D%5Bcity%5D=Scottsdale

Example Response

{
  "id": "87",
  "street": "7153 East Thomas Road",
  "city": "Scottsdale",
  "zipcode": 85251,
  "name": "Phoenix Equipment Rentals",
  "geo": {
    "lat": 33.48034450000001,
    "lng": -111.9271738
  }
}

Potential Errors

  • None

deleteById

Delete a model instance by id from the data source

Definition

DELETE /locations/{id}

Arguments

  • id The model id

Example Request

curl -X DELETE http://localhost:3000/locations/88

Example Response

Potential Errors

  • None

count

Count instances of the model matched by where from the data source

Definition

GET /locations/count

Arguments

  • where The criteria to match model instances

Example Request

Count without where
curl http://localhost:3000/locations/count
Count with a where filter
curl http://localhost:3000/locations/cunt?where%5bcity%5d=Burlingame

Example Response

{
    count: 6
}

Potential Errors

  • None

nearby

Find nearby locations around the geo point

Definition

GET /locations/nearby

Arguments

  • here geo location object with lat and lng properties
  • page number of pages (page size=10)
  • max max distance in miles

Example Request

curl http://localhost:3000/locations/nearby?here%5Blat%5D=37.587409&here%5Blng%5D=-122.338225

Example Response

[
  {
    "id": "88",
    "street": "390 Lang Road",
    "city": "Burlingame",
    "zipcode": 94010,
    "name": "Bay Area Firearms",
    "geo": {
      "lat": 37.5874391,
      "lng": -122.3381437
    }
  },
  {
    "id": "89",
    "street": "1850 El Camino Real",
    "city": "Menlo Park",
    "zipcode": 94027,
    "name": "Military Weaponry",
    "geo": {
      "lat": 37.459525,
      "lng": -122.194253
    }
  }
]

Potential Errors

  • None

updateAttributes

Update attributes for a model instance and persist it into the data source

Definition

PUT /locations/{id}

Arguments

  • data An object containing property name/value pairs
  • id The model id

Example Request

curl -X PUT -H "Content-Type:application/json" -d '{"name": "L2"}' \
http://localhost:3000/locations/88

Example Response

{
  "id": "88",
  "street": "390 Lang Road",
  "city": "Burlingame",
  "zipcode": 94010,
  "name": "L2",
  "geo": {
    "lat": 37.5874391,
    "lng": -122.3381437
  },
  "state": "CA"
}

Potential Errors

  • 404 No instance found for the given id

getAssociatedModel

Follow the relations from one model (location) to another one (inventory) to get instances of the associated model.

Definition

GET /locations/{id}/inventory

Arguments

  • id The id for the location model

Example Request

curl http://localhost:3000/locations/88/inventory

Example Response

[
  {
    "productId": "2",
    "locationId": "88",
    "available": 10,
    "total": 10
  },
  {
    "productId": "3",
    "locationId": "88",
    "available": 1,
    "total": 1
  }
]

Potential Errors

  • None