My journey with

What is loopback?

Main parts

JSON definition

CRUD REST endpoints

ACL

Datasource

Client lib

  • each model has one JS file
  • API endpoint
  • model method
  • operation hook

Custom logic

  • auto-migrate
    • drop all tables and recreate them
    • => useless
  • auto-migrate
    • looks for changes in model definitions
    • can't handle: rename fields, indices
    • => useless
  • no real support

Migrations

Transaction management

  • manually handle opening, committing and rollbacking
  • manually use the transaction when performing DB operations
  • nested transactions?
...

MyModel.beginTransaction(tx => {
  MyModel.findById(42, {}, {transaction: tx})
    .then(modelInstance => {
      return modelInstance.update(
        {attribute: newValue}, 
        {transaction: tx}
      );
    })
    .then(result => {
      return tx.commit().then(() => result);
    })
    .catch(err) => {
      console.error(err.stack);
      tx.rollback();
    });
    
});
function fancyFind(criteria, transaction = null) {
  if (transaction) {
    return FancyModel.find(criteria);
  } else {
    return FancyModel.find(criteria, {transaction});
  }
}

Consequence

Support for promises

The (in)famous Contex

  • update a model in a transaction
  • any model validations need to run in THE SAME transaction
...
return modelInstance.update(
  {attribute: newValue}, 
  {transaction: tx}
);
...
MyModel.validate('new value is ok', () => {
  AnotherModel.find(
    {where: {aValue: this.attribute}},
    {transaction: tx}
  )
});
  • how does tx get from left to right?
    • the CONTEXT (manually)
  • what else uses the context?
    • the loopback way of getting the current user

The datasource juggler

  • allows you to connect the models to any datasource:
    • MySQL
    • Memory
    • PostgreSQL
    • MongoDB
    • REST API
    • ...
  • however:
    • various inconsistencies in storage providers make this very hard to achieve
    • not all datasources use the same code (the juggler)

The API interface

MyModel.find({
  "filter": {
    "where": {
      "id": {
        "inq": [1, 2, 3]
      },
      "startDate": {
        "gte": "2016-01-02"
      }
    },
    "include": {
      "child": {
        "include": ["grandchild"]
      }
    }
  }
});
http://localhost:3010/api/action-plan-items?filter=%7B%22where%22%3A%7B%22id%22%3A%7B%22
inq%22%3A%5B1%2C2%2C3%5D%7D%2C%22startDate%22%3A%7B%22gte%22%3A%222016-01-02%22%7D%7D
%2C%22include%22%3A%7B%22child%22%3A%7B%22include%22%3A%5B%22grandchild%22%5D%7D%7D%7D
http://localhost:3010/api/action-plan-items?filter={"where":{"id":{"inq":[1,2,3]},
"startDate":{"gte":"2016-01-02"}},"include":{"child":{"include":["grandchild"]}}}
  • very flexible
  • one API invocation to get everything you want
  • no need to implement a custom API endpoint

-------------

  • easy to go over the url length limit
  • exposing a lot of internal structure (but that is REST; 1-1 mapping between Model - REST Resource)
  • the correctness relies on using query params with empty string and empty array
  • JS querystring library removes the empty array

Query params

http://localhost:3010/api/action-plan-items?filter={"where":{"id":{"inq":[]}}}
http://localhost:3010/api/action-plan-items

Take aways

  • Very good for rapid prototyping
  • Very good integration with Angular
  • Decent community where you can find resources
  • Multitude of github repos some actively maintained, some not so much
  • Pretty nice to do unit tests

Take aways

  • Doesn't encapsulate really well
  • A lot of manual work when working with transactions
    • Also, hard to test without a MySQL datasource
  • in the long run, you will move away from the generic REST API
  • No migration support
  • Once you get past a certain level, you will get hit by bugs
  • Could be better and more actively maintained

Q & A

Thanks!

Resources

  • TODO

My journey with loopback

By Horia Radu

My journey with loopback

  • 384