Page Contents

Overview

A model describes business domain objects, for example, Customer, Address, and Order. It usually defines a list of properties with name, type, and other constraints.

Models can be used for data exchange on the wire or between different systems. For example, a JSON object conforming to the Customer model definition can be passed in REST/HTTP payload to create a new customer or stored in a document database such as MongoDB. Model definitions can also be mapped to other forms, such as relational database schemas, XML schemas, JSON schemas, OpenAPI schemas, or gRPC message definitions, and vice versa.

There are two subtly different types of models for domain objects:

  • Value Object: A domain object that does not have an identity (ID). Its equality is based on the structural value. For example, Address can be modeled as a Value Object because two US addresses are equal if they have the same street number, street name, city, and zip code values. For example:
{
  "name": "Address",
  "properties": {
    "streetNum": "string",
    "streetName": "string",
    "city": "string",
    "zipCode": "string"
  }
}
  • Entity: A domain object that has an identity (ID). Its equality is based on the identity. For example, Customer can be modeled as an Entity because each customer has a unique customer id. Two instances of Customer with the same customer id are equal since they refer to the same customer. For example:
{
  "name": "Customer",
  "properties": {
    "id": "string",
    "lastName": "string",
    "firstName": "string",
    "email": "string",
    "address": "Address"
  }
}

Currently, we provide the @loopback/repository module, which provides special decorators for adding metadata to your TypeScript/JavaScript classes in order to use them with the legacy implementation of the datasource juggler.

Definition of a Model

At its core, a model in LoopBack is a simple JavaScript class.

export class Customer {
  email: string;
  isMember: boolean;
  cart: ShoppingCart;
}

Extensibility is a core feature of LoopBack. There are external packages that add additional features, for example, integration with the juggler bridge or JSON Schema generation. These features become available to a LoopBack model through the @model and @property decorators from the @loopback/repository module.

import {model, property} from '@loopback/repository';

@model()
export class Customer {
  @property()
  email: string;
  @property()
  isMember: boolean;
  @property()
  cart: ShoppingCart;
}

Model Discovery

LoopBack can automatically create model definitions by discovering the schema of your database. See Discovering models for more details and a list of connectors supporting model discovery.

Using the Juggler Bridge

To define a model for use with the juggler bridge, extend your classes from Entity and decorate them with the @model and @property decorators.

import {model, property, Entity} from '@loopback/repository';

@model()
export class Product extends Entity {
  @property({
    id: true,
    description: 'The unique identifier for a product',
  })
  id: number;

  @property()
  name: string;

  @property()
  slug: string;

  constructor(data?: Partial<Product>) {
    super(data);
  }
}

Models are defined primarily by their TypeScript class. By default, classes forbid additional properties that are not specified in the type definition. The persistence layer respects this constraint and configures underlying PersistedModel classes to enforce strict mode.

LB4 supports creating a model that allows both well-defined but also arbitrary extra properties for NoSQL databases such as MongoDB. You need to disable strict mode in model settings. Besides modifying model settings directly, it can also be done through the CLI by setting Allow additional (free-form) properties to true by saying yes when given this prompt and telling TypeScript to allow arbitrary additional properties to be set on model instances.

@model({settings: {strict: false}})
class MyFlexibleModel extends Entity {
  @property({id: true})
  id: string;

  // Define well-known properties here

  // Add an indexer property to allow additional data
  [prop: string]: any;
}

Model Decorator

The model decorator can be used without any additional parameters, or can be passed in a ModelDefinitionSyntax:

@model({
  name: 'Category',
  settings: {
    // etc...
  },
  // define properties by @property decorator below
})
class Category extends Entity {
  // etc...
  @property({type: 'number'})
  categoryId: number;
}

The model decorator already knows the name of your model class, so you can omit it.

@model()
class Product extends Entity {
  name: string;
  // other properties...
}

As for entries in settings, LoopBack 4 supports these built-in entries for now:

Supported Entries of Model Definition

Property Type Default Description
name String None Name of the model.
settings.description String None Optional description of the model. We only support string type for now. (see issue #3428 for more discussion.)
settings.forceId Boolean true Set it to true to prevent clients from setting the auto-generated ID value manually.
settings.hiddenProperties Array of String None The properties can be hidden from response bodies (.toJSON() output). See Hidden properties section below for details.
settings.scope Object N/A Scope enables you to set a scope that will apply to every query made by the model's repository. See Scope below for more details and examples.
settings.strict Boolean or String true In LB4, the default for this entry is set to be true.
Specifies whether the model accepts only predefined properties or not. One of:
  • true: Only properties defined in the model are accepted. Use if you want to ensure the model accepts only predefined properties. If you try to save a model instance with properties that are not predefined, LoopBack throws a ValidationError. In addition, SQL databases only support this mode.
  • false: The model is an open model and accepts all properties, including ones not predefined in the model. This mode is useful to store free-form JSON data to a schema-less database such as MongoDB and supported by such databases only.
  • "filter": Only properties defined in the model are accepted. If you load or save a model instance with properties that are not predefined, LoopBack will ignore them. This is particularly useful when dealing with old data that you wish to lose without a migration script.

Unsupported Entries

If you’re a LB3 user, there are several entries that are no longer available in LB4:

Property Description
acls (TBD)
base This entry is no longer being used. This is done by the typical Js/Tsc classes inheritance way in LB4:
@model() class MySuperModel extends MyBaseModel {...}
  
excludeBaseProperties (TBD)
http This entry affects HTTP configuration in LB3. Since in LB4 http configurations are inferred from controller members and the rest server, this field is not applicable.
options (TBD) see issue #2142 for further discussion.
plural This entry is part of HTTP configuration in LB3. So it's not applicable for the same reason as http above.
properties This entry is no longer being used as we introduced @property decorator in LB4. See @property decorator below to discover moer about how to define properties for your models.
relations With the introduction of repositories, now relations are defined by relations decorators in LB4. See Relations for more details.
remoting.
normalizeHttpPath
This entry is part of HTTP configuration in LB3. So it's not applicable for the same reason as http above.
replaceOnPUT This entry is no longer supported as LB4 controllers scaffolded by LB4 controller, PUT is always calling replaceById. Users are free to change the generated code to call patchById if needed.

To discover more about Model Decorator in LoopBack 4, please check legacy-juggler-bridge file and model-builder file.

Hidden Properties

The properties are stored in the database, available in JS/TS code, can be set via POST/PUT/PATCH requests, but they are removed from response bodies (.toJSON() output).

To hide a property, you can use the hiddenProperties setting like this:

@model({
  settings: {
    hiddenProperties: ['password']
  }
})
class MyUserModel extends Entity {
  @property({id: true})
  id: number;
   @property({type: 'string'})
  email: string;
   @property({type: 'string'})
  password: string;
  ...
}

Scope

Scope enables you to set a scope that will apply to every query made by the model’s repository.

If you wish for a scope to be applied across all queries to the model, set the scope to do so. For example:

@model({
  settings: {
    scope: {
      limit: 2,
      where: {deleted: false}
    },
  }
})
export class Product extends Entity {
  ...

Now, any CRUD operation with a query parameter runs in the default scope will be applied; for example, assuming the above scope, a find operation such as

await ProductRepository.find({offset: 0});

Becomes the equivalent of this:

await ProductRepository.find({
  offset: 0,
  limit: 2,
  where: {deleted: false},
});

Property Decorator

LoopBack 4 uses the property decorator for property definitions.

@model()
class Product extends Entity {
  @property({
    name: 'name',
    description: "The product's common name.",
    type: 'string',
  })
  public name: string;

  @property({
    type: 'number',
    id: true,
  })
  id: number;
}

Here are general attributes for property definitions:

Key Required? Type Description
default No Any* Default value for the property. The type must match that specified by type. NOTE: if you have both default value set and required set to true, you will still need to include the property in the request body of POST/PUT requests as LoopBack follows the OpenAPI standard that required means the user needs to provide the field in the request always.
defaultFn No String A name of the function to call to set the default value for the property. Must be one of:
  • "guid": generate a new globally unique identifier (GUID) using the computer MAC address and current time (UUID version 1).
  • "uuid": generate a new universally unique identifier (UUID) using the computer MAC address and current time (UUID version 1).
  • "uuidv4": generate a new universally unique identifier using the UUID version 4 algorithm.
  • "now": use the current date and time as returned by new Date()
NOTE: the value of defaultFn is generated by LoopBack itself. If you'd like to use database built-in uuid functions (MySQL or Postgres for example), please check the README file of the corresponding connector.
description No String or Array Documentation for the property. You can split long descriptions into arrays of strings (lines) to keep line lengths manageable. For example:
[
"LoopBack 4 is a highly extensible Node.js and TypeScript framework",
"for building APIs and microservices.",
"Follow us on GitHub: https://github.com/strongloop/loopback-next."
]
doc No String Documentation for the property. Deprecated, use "description" instead.
id No Boolean Whether the property is a unique identifier. Default is false. See ID properties section below for detailed explanations.
index No Boolean Whether the property represents a column (field) that is a database index.
required No Boolean Whether a value for the property is required in the request body for creating or updating a model instance.

Default is false. NOTE: As LoopBack follows the OpenAPI standard, required means the user needs to provide the field in the request always. POST/PUT requests might get rejected if the request body doesn't include the required property even it has default value set.
type Yes String Property type. Can be any type described in LoopBack types.

ID Properties

LoopBack 4 expects a model to have one ID property that uniquely identifies the model instance.

To explicitly specify a property as ID, set the id property of the option to true. The id property value must be one of:

  • true: the property is an ID.
  • false (or any value that converts to false): the property is not an ID (default).

In database terms, the ID property is primary key column. Such properties are defined with the ‘id’ attribute set to true.

For example,

  @property({
    type: 'number',
    id: true,
  })
  id: number;

In LoopBack, auto-migration helps the user create relational database schemas based on definitions of their models. Here are some id property settings that can be used for auto-migration / auto-update:

Key Required? Type Description
generated No Boolean For auto-migrate usage. The generated property indicates the ID will be automatically generated by the database. When it is set to true, the value of the id property will be generated by the database automatically with its default type (e.g integer for MySQL and string for MongoDB).
useDefaultIdType No Boolean For auto-migrate usage. Set it to false when it's needed to auto-generate non-default type property values. For example, for PostgreSQL, to use uuid as the id property, the id type should set to string, generated should set to true, and this field should be set to false. Please check each connector's README file for more information about auto-migration/auto-update.

Tips:

  1. LoopBack CRUD methods expect the model to have an “id” property if the model is backed by a database.
  2. A model without any “id” properties can only be used without attaching to a database.
  3. If an ID property has generated set to true, the connector decides what type to use for the auto-generated key. For example for SQL Server, it defaults to number. This can be overwritten by setting useDefaultIdType to false.
  4. Check out Database Migration if you’d like to have LoopBack 4 create relational database’s schemas for you based on model definitions. Always check Database Connectors for details and examples for database migration / discover.

Data Mapping Properties

When using a relational database data source, LB4 allows you to describe tables via the model definition and/or property definition.

The following fields of settings of the model definition describe the table in the database:

Property Type Description
[connector name].schema String schema of the table
[connector name].table String the table name

The following are common fields of the property definition that describe the columns in the database:

Property Type Description
columnName String Column name
dataType String Data type as defined in the database
dataLength Number Data length
dataPrecision Number Numeric data precision
dataScale Number Numeric data scale
nullable Boolean If true, data can be null

For example, to map a property to a column in an PostgreSQL database table, use the following:

@model({
  settings: {
    postgresql: {schema: 'public', table: 'mymodel'},
  },
})
export class MyModel extends Entity {
  @property({
    type: 'number',
    required: false,
    scale: 0,
    id: true,
    postgresql: {
      columnName: 'testId',
      dataType: 'integer',
      dataLength: null,
      dataPrecision: null,
      dataScale: 0,
      nullable: 'NO',
    },
  })
  id: number;
...
}

Notice that with the mapping, the model property name (id) and the corresponding database column name (testId) can be different if needed.

Non-public Information
Removed until https://github.com/strongloop/loopback-datasource-juggler/issues/128 is resolved.

Conversion and formatting properties

Format conversions are declared in properties, as described in the following table:

Key Type Description
trim Boolean Whether to trim the string
lowercase Boolean Whether to convert a string to lowercase
uppercase Boolean Whether to convert a string to uppercase
format Regular expression Format for a date property.

Array Property Decorator

There is a limitation to the metadata that can be automatically inferred by LoopBack, due to the nature of arrays in JavaScript. In JavaScript, arrays do not possess any information about the types of their members. By traversing an array, you can inspect the members of an array to determine if they are of a primitive type (string, number, array, boolean), object or function, but this does not tell you anything about what the value would be if it were an object or function.

For consistency, we require the use of the @property.array decorator, which adds the appropriate metadata for type inference of your array properties.

@model()
class Order extends Entity {
  @property.array(Product)
  items: Product[];
}

@model()
class Thread extends Entity {
  // Note that we still require it, even for primitive types!
  @property.array(String)
  posts: string[];
}

Additionally, the @property.array decorator can still take an optional second parameter to define or override metadata in the same fashion as the @property decorator.

@model()
class Customer extends Entity {
  @property.array(String, {
    name: 'names',
    required: true,
  })
  aliases: string[];
}

Extra attributes for json schema can be supplied via the jsonSchema within the second parameter.

@model()
class Customer extends Entity {
  @property(String, {
    jsonSchema: {
      format: 'email',
    },
  })
  email: string;
}

For @property.array, the jsonSchema is for the item type instead of the array itself.

@model()
class TestModel {
  @property.array(String, {
    jsonSchema: {
      format: 'email',
      minLength: 5,
      maxLength: 50,
      transform: ['toLowerCase'],
    },
  })
  emails?: string[];
}

Validation Rules

You can also specify the validation rules in the field jsonSchema. For example:

@model()
class Product extends Entity {
  @property({
    name: 'name',
    description: "The product's common name.",
    type: 'string',
    // Specify the JSON validation rules here
    jsonSchema: {
      maxLength: 30,
      minLength: 10,
      errorMessage:
        'name must be at least 10 characters and maximum 30 characters',
    },
  })
  public name: string;
}

Check out the documentation of Parsing requests to see how to do it in details.

The property decorator leverages LoopBack’s metadata package to determine the type of a particular property.

Example:

export class StandardUser {
  public email: string;
  public anotherProperty: boolean;
}

@model()
export class UserModel {
  @property()
  public email: StandardUser['email']; // => results in \"__metadata(\"design:type\", Object)\" instead of \"__metadata(\"design:type\", String)\"
}

(see Issue #3863 for more details)

@model()
class Product extends Entity {
  @property()
  public name: string; // The type information for this property is String.
}

JSON Schema Inference

Use the @loopback/repository-json-schema module to build a JSON schema from a decorated model. Type information is inferred from the @model and @property decorators. The @loopback/repository-json-schema module contains the getJsonSchema function to access the metadata stored by the decorators to build a matching JSON schema of your model.

import {model, property} from '@loopback/repository';
import {getJsonSchema} from '@loopback/repository-json-schema';

@model()
class Category {
  @property()
  name: string;
}

@model()
class Product {
  @property({required: true})
  name: string;
  @property()
  type: Category;
}

const jsonSchema = getJsonSchema(Product);

jsonSchema from above would return:

{
  "title": "Product",
  "properties": {
    "name": {
      "type": "string"
    },
    "type": {
      "$ref": "#/definitions/Category"
    }
  },
  "definitions": {
    "Category": {
      "properties": {
        "name": {
          "type": "string"
        }
      }
    }
  },
  "required": ["name"]
}

If a custom type is specified for a decorated property in a model definition, then a reference $ref field is created for it and a definitions sub-schema is created at the top-level of the schema. The definitions sub-schema is populated with the type definition by recursively calling getJsonSchema to build its properties. This allows for complex and nested custom type definition building. The example above illustrates this point by having the custom type Category as a property of our Product model definition.

Supported JSON Keywords

Following are the supported keywords that can be explicitly passed into the decorators to better tailor towards the JSON Schema being produced:

Keywords Decorator Type Default Description
title @model string model name Name of the model
description @model string None Description of the model
array @property boolean None Used to specify whether the property is an array or not
required @property boolean false Used to specify whether the property is required or not

Other ORMs

You might decide to use an alternative ORM/ODM in your LoopBack application. LoopBack 4 no longer expects you to provide your data in its own custom Model format for routing purposes, which means you are free to alter your classes to suit these ORMs/ODMs.

However, this also means that the provided schema decorators will serve no purpose for these ORMs/ODMs. Some of these frameworks may also provide decorators with conflicting names (e.g. another @model decorator), which might warrant avoiding the provided juggler decorators.

FAQ

Feel overwhelmed? Here are some examples of setting up models: