Loopback

Fast, flexible API framework

Chat app

Make a simple Single Page App, with a front and a API.
 

I want to follow these 3 basics steps :

 

  1. Implement a basic front with a basic CRUD API
  2. Add Google sign-in with email blacklist (private beta)
  3. Be able to make improvements without big refactoring

I want the first step to be done very fast

Which frameworks use ?

 

  • For a front
    • Angular, Ember or React allow fast prototyping
    • Bootstrap, Material Design allow fast proto too
  • For hosting
    • AWS, Openshift, Heroku
  • An API Server, with a Database
    • Firebase, Rails, Parse, MEAN stack ?

Problem

  • Firebase
    • No advanced OAuth sign-in configuration
  • Rails app
  • Parse
    • self-hosted, lack of NPM modules support
  • MEAN stack
    • DIY, lot of work
    • even with NPM modules
  • Node.js
  • Express based
  • Fast prototyping
  • Performance
  • Well documented

Loopback

Tools

Generators

  • Application generator : slc loopback.
  • Model generator: slc loopback:model.  
  • Property generator: slc loopback:property
  • Data sources generator: slc loopback:datasource.
  • Relationship generator: slc loopback:relation.
  • ACL generator: slc loopback:acl.

API explorer

API Composer

Other stuff

  • Monitoring
  • Cluster mode
  • Database discovery
    • from schema
    • from data
  • Swagger generator
    • create model from definition

Features

Models

Each model is connected to a datasource

Built-in connectors

  • Memory
  • MongoDB
  • MySQL
  • Oracle
  • PostgreSQL
  • SQL Server
  • Email Connector
  • Push Connector
  • REST Connector
  • SOAP Connector
  • Storage Connector

Missing a connector ? You can still develop your own.

Built-in models

  • Model

  • PersistedModel

  • Access token

  • ACL

  • Application

  • Installation

  • Push Notification

  • Relation

  • Role

  • User

PersistedModel

This model provide following features to any child model.

User

User extend PersistedModel and add the following features :

ACL

Concepts

 

  • PrincipalAn entity that can be identified or authenticated.
     
  • RoleA group of principals with the same permissions.
     
  • RoleMappingAssign principals to roles

ACL

// common/models/<model>.json
{
 // ...
 "acls": [
    {
      "permission": "ALLOW",
      "principalType": "ROLE",
      "principalId": "$everyone",
      "property": "myMethod"
    }, 
    ...
  ]
}

ACL

Components

  • Push Notifications: Adds push notification capabilities to your LoopBack application.
     
  • Storage service: Adds an interface to abstract storage providers like S3, filesystem into general containers and files.
     
  • Third-party login: Adds third party login capabilities to your LoopBack application like Facebook, GitHub, etc.
     
  • Synchronization: Adds replication capability between LoopBack running in a browser or between LoopBack back-end instances to enable offline synchronization and server-to-server data synchronization.

Let's make this app

Features

  • Google Sign-in

    • Private beta: email whitelist

  • Users can create discussion and invite other users

  • Users can only see/access discussions their are member of.

Models

(Users can only access/see discussions they are member of.)

Scaffold our application

Create a datasource config

> slc loopback:datasource
// server/datasources.json
{
  "db": {
    "name": "db",
    "connector": "memory"
  },
  "mongodb": {
    "name": "mongodb",
    "database": "loopback_test_dev",
    "password": "",
    "connector": "mongodb",
    "user": ""
  }
}

Create models

> slc loopback:model
// common/models/discussion.json
{
  "name": "Discussion",
  "plural": "Discussions",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "title": {
      "type": "string",
      "required": true
    },
    "open": {
      "type": "boolean",
      "default": true
    }
  },
  "validations": [],
  "relations": {},
  "acls": [],
  "methods": []
}
// common/models/message.json
{
  "name": "Message",
  "plural": "Messages",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "content": {
      "type": "string",
      "required": true
    }
  },
  "validations": [],
  "relations": {},
  "acls": [],
  "methods": []
}

Create models relations

> slc loopback:relation
// common/models/message.json

{
  "name": "Message",
  // ...
  "properties": {
    // ...
  },
  "validations": [],
  "relations": {
    "user": {
      "type": "hasOne",
      "model": "User",
      "foreignKey": "userID"
    }
  },
  "acls": [],
  "methods": []
}

Create models ACL

> slc loopback:acl

Users can only access/see discussions they are member of

  1. Deny all operations on Discussion model
  2. Allow all operations on all methods for "memberOf" Role
  3. Allow create method for all authenticated users

Create models ACL

// common/models/discussion.json

{
  "name": "Discussion",
  // ...
  "acls": [
    {
      "accessType": "READ",
      "principalType": "ROLE",
      "principalId": "$everyone",
      "permission": "DENY"
    },
    {
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "memberOf",
      "permission": "ALLOW"
    },
    {
      "accessType": "EXECUTE",
      "principalType": "ROLE",
      "principalId": "$authenticated",
      "permission": "ALLOW",
      "property": "create"
    }
  ],
  "methods": []
}

Create models ACL

// server/boot/script.js

module.exports = function(app) {
  var Role = app.models.Role;
  Role.registerResolver('memberOf', function(role, context, cb) {
    function reject(err) {
      if(err) {
        return cb(err);
      }
      cb(null, false);
    }
    if (context.modelName !== 'discussion') {
      // the target model is not discussion
      return reject();
    }
    var userId = context.accessToken.userId;
    if (!userId) {
      return reject(); // do not allow anonymous users
    }

    context.model.findById(context.modelId, function(err, discussion) {
      if(err || !discussion) {
        reject(err);
      }
      var DiscussionMembers = app.models.DiscussionMembers;
      DiscussionMembers.count({
        discussionId: discussion.id,
        userId: userId
      }, function(err, count) {
        if (err) {
          return reject(err);
        }
        cb(null, count > 0); // true = is a team member
      });
    });
  });
};

Google Sign-in

Using loopback-component-passport

  • UserIdentity model: keeps track of third-party login profiles.

  • UserCredential model: stores credentials from a third-party provider to represent users’ permissions and authorizations.

  • ApplicationCredential model: stores credentials associated with a client application.

  • PassportConfigurator: the bridge between LoopBack and Passport.

Google Sign-in

Using loopback-component-passport

// server/providers.json

{
   "google-login": {
    "provider": "google",
    "module": "passport-google-oauth",
    "strategy": "OAuth2Strategy",
    "clientID": "<clientID>",
    "clientSecret": "<clientSecret>",
    "callbackURL": "<callbackURL>",
    "authPath": "/auth/google",
    "callbackPath": "/auth/google/callback",
    "successRedirect": "/auth/account",
    "scope": ["email", "profile"]
  }
}

Google Sign-in

Using loopback-component-passport

// server/server.js

var loopback = require('loopback');
var path = require('path');
var app = module.exports = loopback();
// Create an instance of PassportConfigurator with the app instance
var PassportConfigurator = require('loopback-component-passport').PassportConfigurator;
var passportConfigurator = new PassportConfigurator(app);
 
app.boot(__dirname);
...
// Enable http session
app.use(loopback.session({ secret: 'keyboard cat' }));
 
// Load the provider configurations
var config = {};
try {
 config = require('./providers.json');
} catch(err) {
 console.error('Please configure your passport strategy in `providers.json`.');
 console.error('Copy `providers.json.template` to `providers.json` and replace the clientID/clientSecret values with your own.');
 process.exit(1);
}
// Initialize passport
passportConfigurator.init();
 
// Set up related models
passportConfigurator.setupModels({
 userModel: app.models.user,
 userIdentityModel: app.models.userIdentity,
 userCredentialModel: app.models.userCredential
});
// Configure passport strategies for third party auth providers
for(var s in config) {
 var c = config[s];
 c.session = c.session !== false;
 passportConfigurator.configureProvider(s, c);
}

Loopback is backed by StrongLoop

According to GitHub, StrongLoop [...] made over 420 commits comprising a total contribution of almost 630,000 lines of code to the upcoming v0.12 [node.js] release. These contributions made StrongLoop the largest individual and corporate contributor to v0.12.

Questions ?

@wittydeveloper

Loopback

By Charly Poly

Loopback

Loopback node.js framework presentation

  • 3,398