loopback-datasource-juggler/docs/ldl.md

7.8 KiB

LoopBack Definition Language (LDL)

LoopBack Definition Language (LDL) is simple DSL to define data models in JavaScript or plain JSON. With LoopBack, we often start with a model definition which describes the structure and types of data. The model establishes common knowledge of data in LoopBack.

Describing a model

Let's start with a simple example in plain JSON.

{
    "id": "number",
    "firstName": "string",
    "lastName": "string"
}

The model simply defines a user model that consists of three properties:

  • id - The user id. It's a number.
  • firstName - The first name. It's a string.
  • lastName - The last name. It's a string.

Each key in the JSON object defines a property in our model which will be cast to its associated type. The simplest form of a property definition is propertyName: type. The key is the name of the property and the value is the type of the property. We'll cover more advanced form later in this guide.

LDL supports a list of built-in types, including the basic types from JSON:

  • String
  • Number
  • Boolean
  • Array
  • Object

Note: The type name is case-insensitive, i.e., either "Number" or "number" can be used.

The same model can also be described in JavaScript code:

var UserDefinition = {
    id: Number,
    firstName: String,
    lastName: String
}

As we can see, the JavaScript version is less verbose as it doesn't require quotes for property names. The types are described using JavaScript constructors, for example, Number for "Number". String literals are also supported.

Now we have the definition of a model, how do we use it in LoopBack Node.js code? It's easy, LoopBack will build a JavaScript constructor (or class) for you.

Creating a model constructor

LDL compiles the model definition into a JavaScript constructor using ModelBuilder.define APIs. ModelBuilder is the basic factory to create model constructors.

ModelBuilder.define() method takes the following arguments:

  • name: The model name

  • properties: An object of property definitions

  • options: An object of options, optional

    var ModelBuilder = require('loopback-datasource-juggler').ModelBuilder;

    // Create an instance of the ModelBuilder var modelBuilder = new ModelBuilder();

    // Describe the user model var UserDefinition = { id: Number, firstName: String, lastName: String }

    // Compile the user model definition into a JavaScript constructor var User = modelBuilder.define('User', UserDefinition);

    // Create a new instance of User var user = new User({id: 1, firstName: 'John', lastName: 'Smith'});

    console.log(user.id); // 1 console.log(user.firstName); // 'John' console.log(user.lastName); // 'Smith'

That's it. Now you have a User constructor representing the user model.

At this point, the constructor only has a set of accessors to model properties. No behaviors have been introduced yet.

Adding methods to a model constructor

There are a few ways to add methods to a model constructor:

  1. Create the model constructor from a data source

     var DataSource = require('loopback-datasource-juggler').DataSource;
     var ds = new DataSource('memory');
    
     // Compile the user model definition into a JavaScript constructor
     var User = ds.define('User', UserDefinition);
    
     // Create a new instance of User
     User.create({id: 1, firstName: 'John', lastName: 'Smith'}, function(err, user) {
         console.log(user); // The newly created user instance
         User.findById(1, function(err, user) {
             console.log(user); // The user instance for id 1
             user.firstName = 'John1'; // Change the property
             user.save(function(err, user) {
                 console.log(user); // The modified user instance for id 1
             });
         };
     });
    
  2. Attach the model to a data source

A plain model constructor created from ModelBuilder can be attached a DataSource.

    var DataSource = require('loopback-datasource-juggler').DataSource;
    var ds = new DataSource('memory');

    User.attachTo(ds); // The CRUD methods will be mixed into the User constructor
  1. Manually declare methods to the model constructor

We can add static and prototype methods to a model constructor.

// Define a static method
User.greet = function(msg) {
    console.log('Hello ', msg);
};

// Define a prototype method
User.prototype.getFullName = function () {
    return this.firstName + ' ' + this.lastName;
};

User.greet('world'); // prints 'Hello world'
var user = new User({id: 1, firstName: 'John', lastName: 'Smith'});
console.log(user.getFullName()); // 'John Smith'

Exploring advanced features

The basic example use propertyName: type to describe a property.

Properties can have options in addition to the type. LDL uses a JSON object to describe such properties, for example:

"id": {"type": "number", "id": true, "doc": "User ID"}

"firstName": {"type": "string", "required": true, "oracle": {"column": "FIRST_NAME", "type": "VARCHAR(32)"}}

Common options for a property are:

  • type: The property type

    • String/Text
    • Number
    • Date
    • Boolean
    • Buffer/Binary
    • Array
    • Any/Object/JSON
    • GeoPoint
  • id: Indicate if the property is an id of the model. The value can be true, false, or a number

    • true: It's an id
    • false: It's not an id
    • 0: It's not an id
    • 1: It's the first part of the composite id

LDL supports the definition of a composite id that has more than one properties. For example,

var InventoryDefinition =
{
    productId: {type: String, id: 1},
    locationId: {type: String, id: 2},
    qty: Number
}

The composite id is (productId, locationId) for an inventory model.

  • doc: Documentation of the property

  • default: The default value of the property

Constraints are modeled as options, for example:

  • required: Indicate if the property is required
  • pattern: A regular expression pattern that a string should match
  • min/max: The minimal and maximal value
  • length: The maximal length of a string

Format conversions can also be declared as options, for example:

  • trim: Trim the string
  • lowercase: Convert the string to be lowercase
  • uppercase: Convert the string to be uppercase
  • format: Format a Date

Data source specific mappings can be added to the property options, for example, to map a property to be a column in Oracle database table, you can use the following syntax:

"oracle": {"column": "FIRST_NAME", "type": "VARCHAR", "length": 32}

Array types

LDL supports array types as follows:

{emails: [String]}

or

{"emails": ["String"]}

or

{emails: [{type: String, length: 64}]}

Object types

Embed anonymous types

Reference named types

Advanced example

var User = modelBuilder.define('User', {
    name: String,
    bio: ModelBuilder.Text,
    approved: Boolean,
    joinedAt: Date,
    age: Number,
    address: {
        street: String,
        city: String,
        state: String,
        zipCode: String,
        country: String
    },
    emails: [{
        label: String,
        email: String
    }],
    friends: [String]
});

Model level options

LDL uses a list of options to control the definition of a model.

  • strict:
  • idInjection:
  • Data source specific mappings

Relations between models

// setup relationships
User.hasMany(Post,   {as: 'posts',  foreignKey: 'userId'});

Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});

User.hasAndBelongsToMany('groups');

Extend from a base model

Mix in model definitions

var Group = modelBuilder.define('Group', {group: String});

User.mixin(Group);