These release notes describe what has changed in LoopBack 3.0.

See also:

Page Contents

For instructions on migrating a LoopBack 2.x application, see the 3.0 Migration Guide.

Installation

See Installation.

Changes to strong-remoting

Serialization of date values in responses

The format of date values has been changed from the output of .toString(), which produces values in local timezone, to the output of .toJSON(), which produces values in GMT.

For example, consider new Date(0) returned for dateArgument.

In strong-remoting 2.x, this value is converted to the following response when running on a computer in the Central-European timezone:

{
  "dateArgument": {
    "$type": "date",
    "$data": "Thu Jan 01 1970 01:00:00 GMT+0100 (CET)"
  }
}

In strong-remoting 3.x, the same value is converted to the following response regardless of the server timezone settings:

{
  "dateArgument": {
    "$type": "date",
    "$data": "1970-01-01T00:00:00.000Z"
  }
}

For more information, see strong-remoting#344.

Cleanup in conversion and coercion of input arguments

We have significantly reworked conversion and coercion of input arguments when using the default REST adapter. The general approach is to make both conversion and coercion more strict. When we are not sure how to treat an input value, we rather return HTTP error 400 Bad Request than coerce the value incorrectly.

Most notable breaking changes:

  • null value is accepted only for “object”, “array” and “any”.
  • Empty string is coerced to undefined to support ES6 default arguments.
  • JSON requests providing scalar values for array-typed argument are rejected.
  • Empty value is not converted to an empty array.
  • Array values containing items of wrong type are rejected. For example, an array containing a string value is rejected when the input argument expects an array of numbers.
  • Array items from JSON requests are not coerced. For example, [true] is no longer coerced to [1] for number arrays, and the request is subsequently rejected.
  • Deep members of object arguments are no longer coerced. For example, a query like ?arg[count]=42 produces { count: '42' } now.
  • “any” coercion preserves too large numbers as a string, to prevent losing precision.
  • Boolean types accepts only four string values: ‘true’, ‘false’, ‘0’ and ‘1’. Values are case-insensitive, i.e. ‘TRUE’ and ‘FaLsE’ work too.
  • Date type detects “Invalid Date” and rejects such requests.
  • When converting a value coming from a string source like querystring to a date, and the value is a timestamp (i.e. an integer), we treat the value as a number now. That way the value “0” always produces “1970-01-01T00:00:00.000Z” instead of some date around 1999/2000/2001 depending on server timezone.
  • Array values are not allowed for arguments of type object.

Hopefully these changes should leave most LoopBack applications (and clients) unaffected. If your start seeing unusual amount of 400 error responses after upgrading to LoopBack 3.x, then check the client code and ensure it correctly encodes request parameters.

For more information, see the following pull requests:

The name “file” is reserved now

The name “file” (and all other casing variations like “File”) is a reserved type now and cannot be used for custom user-provided types. The most notable result is that LoopBack applications can no longer have a “File” model.

See the following resources for more information:

New REST adapter error handler

The REST adapter (typically created via loopback.rest()) uses a new error handler implementation that provides more secure configuration out of the box.

In production mode (default):

  • HTTP responses never return stack traces.
  • Bad Request errors (4xx) provide the following properties copied from the error object: name, message, statusCode and details.
  • All other errors (including non-Error values like strings or arrays) provide only basic information: statusCode and message set to status name from HTTP specification.

In debug mode (insecure):

  • HTTP responses include all properties provided by the error object.
  • For errors that are not an object, their string value is returned in message field.

The default mode is production. Debug mode can be enabled by setting the debug param to true via middleware configuration. It is recommended to use environment-specific configuration to avoid accidentally enabling debug mode in your production environment.

The error.status is replaced by error.statusCode, which is more descriptive and avoids ambiguity.
If your application uses status, you must change it to statusCode.

For more information, see:

Changes to PersistedModel

Array input for updateOrCreate function is no longer allowed

Allowing updateOrCreate() to accept an array as input would only work when creating new model instances (not updating them), so this support has been removed. Use the create() function instead.

For more information, see loopback-datasource-juggler PR #889.

Full replace vs. partial update

There are two ways to update an existing record (model instance):

  • Update (patch) a subset of model properties only, for example update the address field while keeping other properties like email intact.

  • Replace the whole instance with the data provided in the request.

This distinction is important when working with NoSQL databases like MongoDB, where it’s not possible to unset a property via a partial update.

// in database
{ "name": "ibm", "twitter": "@ibm" }

// patch request (user deleted the twitter field)
{ "name": "ibm", "twitter": undefined }

// result
{ "name": "ibm", "twitter": "@ibm" }

Three new data-access operations perform a full replace:

  • PersistedModel.prototype.replaceAttributes(data, options, cb)
  • PersistedModel.replaceById(id, data, options, cb)
  • PersistedModel.replaceOrCreate(data, options, cb)

To clarify their behavior, the following methods were renamed:

  • updateAttributes was renamed to patchAttributes.
  • updateOrCreate was renamed to patchOrCreate.

The old names are preserved as aliases for backwards compatibility.

The biggest change is in the REST API exposed by LoopBack. By default, PUT endpoints are mapped to replace methods in 3.0 and PATCH endpoints are introduced to expose the old update/patch methods.

HTTP endpoint 3.x method 2.x method
PUT /MyModels replaceOrCreate() updateOrCreate
PUT /MyModels/:id replaceById() prototype.updateAttributes
PATCH /MyModels updateOrCreate() updateOrCreate
PATCH /MyModels/:id prototype.updateAttributes() prototype.updateAttributes

Because this change could be disruptive, we implemented several measures to simplify the upgrade path:

  • A new model-level setting replaceOnPUT that can be used to opt into the 2.x HTTP mapping.
  • Both new methods and the model-level setting were back-ported to 2.x, allowing applications to opt into the new 3.x behaviour while still running on LoopBack 2.x

For more information, see loopback#2316, loopback-datasource-juggler#788, replaceOnPUT flag and related documentation.

For more information, see Migrating apps to LoopBack 3.0.

PersistedModel.find provides ctx.data in “loaded” hook

In LoopBack 2.x, the “loaded” hook for DAO.find method mistakenly set ctx.instance instead of ctx.data. This defeats the purpose of the “loaded” hook: to allow hooks to modify the raw data provided by the datasource before it’s used to build a model instance.

This has been fixed in 3.0 and the “loaded” hook now consistently provides ctx.data for all operations. If you have a “loaded” hook handler that checks if (ctx.instance) then you can remove this condition together with the branch that follows.

For more information, see loopback-datasource-juggler@30283291.

PersistedModel.create method no longer returns created instances

While implementing support for promises in PersistedModel.create(), we found that this method returns the instance object synchronously. This is inconsistent with the usual convention of accessing the result via a callback/promise and it may not work correctly when the data source generates some fields (for example the id property).

In version 3.0, the API has changed to be consistent with other DAO methods:

  • When invoked with a callback argument, PersistedModel.create() does not return anything.
  • When invoked without a callback, PersistedModel.create() returns a promise.

For more information, see loopback-datasource-juggler#918.

Models with auto-generated IDs reject user-provided ID values

For security reasons, the default value of the config variable forceId is set to true whenever a model uses an auto-generated ID. Thus, creating/updating an instance with a client-provided ID returns an error message like:

Unhandled rejection ValidationError: The `MyModel` instance is not valid.
Details: `id` can't be set (value: 1).

To disable the check, set forceId: false in the model JSON file, for example:

{
  "name": "Product",
  "forceId": false
}

For more information, see loopback-datasource-juggler#982.

Changes to LoopBack middleware

CORS is no longer enabled

Two types of applications are commonly built using LoopBack, and each type has different requirements with regard to cross-origin resource sharing (CORS).

  1. Public-facing API servers with a variety of clients. CORS is enabled to allow third-party sites to access the API server that’s on a different domain.

  2. API / single-page application (SPA) sites where both API and the SPA client are on the same domain and the API is locked down to serve only its own browser clients, so CORS is disabled.

By enabling CORS by default, Loopback 2.x made API+SPA sites vulnerable by default.

Strong-remoting 3.x removes the built-in CORS middleware and makes the application developer responsible for enabling and configuring CORS.

For more information, see:

Current context API and middleware removed

The following current-context-related APIs were removed:

  • loopback.getCurrentContext()
  • loopback.createContext()
  • loopback.runInContext()

Additionally, loopback#context middleware and remoting.context server configuration setting were removed too.

Existing applications using current-context will print the following error message when they receive their first HTTP request:

Unhandled error for request GET /api/Users:
Error: remoting.context option was removed in version 3.0.
For more information, see https://loopback.io/doc/en/lb3/Using-current-context.html
for more details.
    at restApiHandler (.../node_modules/loopback/server/middleware/rest.js:44:15)
    at Layer.handle [as handle_request] (.../node_modules/express/lib/router/layer.js:95:5)
    ...

For more information, see:

Removed getters for Express 3.x middleware

Express 4.x stopped bundling commonly-used middleware. To simplify migration of LoopBack 1.x applications (powered by Express 3.x) to LoopBack 2.x (powered by Express 4.x), we created getter properties to allow developers to keep using the old convention.

We have removed these getters in LoopBack 3.0. Here is the full list of removed properties together with the middleware module name to use instead:

Version 2 Version 3
loopback.compress require('compression')
loopback.cookieParser require('cookie-parser')
loopback.cookieSession require('cookie-session')
loopback.csrf require('csurf')
loopback.directory require('serve-index')
loopback.errorHandler require('errorhandler')
loopback.favicon require('serve-favicon')
loopback.logger require('morgan')
loopback.methodOverride require('method-override')
loopback.responseTime require('response-time')
loopback.session require('express-session')
loopback.timeout require('connect-timeout')
loopback.vhost require('vhost')

We have also removed loopback.mime, which was always set to undefined.

For more information, see loopback#2349.

loopback.errorhandler was removed

The loopback#errorhandler middleware was removed. Use strong-error-handler instead.

For more information, see:

Changes to remote methods

Name indicates whether method is static

In 2.x, the isStatic property indicated that a remote methods was a static method (not an instance method).

In 3.0, the isStatic property is no longer used and the method name determines whether a remote method is static or an instance method. A method name starting with prototype. indicates an instance method (isStatic: false), otherwise (by default), a remote method is a static method.

For more information, see loopback#2174.

Clean-up of API for finding and disabling remote methods

The strong-remoting functions SharedClass.prototype.find and SharedClass.prototype.disableMethod for looking up and disabling remote methods are now deprecated. They are superseded by SharedClass.prototype.findMethodByName and SharedClass.prototype.disableMethodByName where you can pass in a staticmethod or a prototype method name. These new methods will accept a string in the form of “name” for a static method and “prototype.name” for a prototype method.

To find a static remote method:

findMethodByName('create')

To find a prototype remote method:

findMethodByName('prototype.updateAttributes')

To disable a static remote method:

disableMethodByName('create')

To disable a prototype remote method:

disableMethodByName('prototype.updateAttributes')

There are corresponding Model methods Model.disableRemoteMethod and Model.disableRemoteMethodByName

To disable a remote method:

MyModel.disableRemoteMethodByName('my_method');

Changes to LoopBack models

Undefined model mixins throw an error

In version 2.x, applying an undefined mixin to a LoopBack model generated a warning message, which you needed to address rather than ignore. In 3.0 an undefined mixin applied to a LoopBack model throws an error that your application must handle.

For more information, see loopback-datasource-juggler#944.

Removed deprecated model events

Version 3.0 eliminates the following deprecated PersistedModel events:

  • changed
  • deletedAll
  • deleted

Instead of listening for these events, use Operation Hooks instead, notably “after save” and “after delete”.

For more information, see loopback-datasource-juggler#965.

Model property name with a dot generates an error

When a model property name contains a dot (period) character, LoopBack throws an error instead of showing a deprecation warning; for example, customer.name: 'string'.

For more information, see loopback-datasource-juggler#947.

Settings strict: validate and strict: throw were removed

The configuration of strict mode is now simplified to a simple Boolean. These settings are no longer supported:

  • strict: 'validate'
  • strict: 'throw'

Given the following definition of a User model, when a new User instance is created with extra (unknown) properties:

ds.define('User', {
  name: String, required: false,
  age: Number, required: false
});

var johndoe = new User({ name: 'John doe', age: 15, gender: 'm'});

The behavior of strict: false remains unchanged and unknown properties are saved along with other properties

// johndoe is created as
{
  id: 1,
  name: 'John doe',
  age: 15,
  gender: 'm'
}

Because a non-empty string is considered “truthy”, they share the same behavior as strict: true

The behavior of strict: true changes as follows:

Version 2.x

strict:true was silently removing unknown properties. As a result, a User instance is created with unknown properties discarded:

johndoe.isValid(); //true

// johndoe is created as
{
  id: 1,
  name: 'John doe',
  age: 15
}

Version 3.0

johndoe.isValid(); //false

In this example, johndoe is NOT created. The create operation fails with the following error:

ValidationError: The User instance is not valid.
Details: gender is not defined in the model (value: 'm').

For more information, see loopback-datasource-juggler#1084.

Unused User model properties removed

The following properties are removed from the built-in User model in 3.0:

  • credentials
  • challenges
  • status
  • created
  • lastUpdated

If your application uses any of these properties, you must define them in your model JSON: see Migrating-to-3.0.html for instructions.

For more information, see loopback#2299.

Other changes

Node.js versions 0.10 and 0.12 are no longer supported

According to Node.js LTS schedule, Node.js v0.10 was supported until October 2016 and Node.js v0.12 until the end of 2016. There will be no more OpenSSL security fixes coming to these release lines after they drop from the LTS program.

Keeping support for these legacy versions would be too expensive, therefore LoopBack 3.0+ supports only Node.js v4 and newer.

See loopback#2807 for the discussion that led to this decision.

loopback-datasource-juggler is now a regular dependency of loopback

Originally, LoopBack (ab)used peer dependencies to ensure there was only one instance of loopback-datasource-juggler in the dependency tree, so that there would only be one singleton instance of the model registry. This was very fragile and problematic in some edge cases.

In version 3.0, loopback-datasource-juggler and data source connectors no longer rely on a single loopback-datasource-juggler instance, which is now a regular dependency of loopback. The change from a peer dependency to a regular dependency was a change in the other loopback packages that depend on loopback-datasource-juggler.

For more information, see loopback#275.

For more information, see how to migrate

Use bluebird for promises

LoopBack version 3.0 uses bluebird as the promise library instead of global.Promise. Starting with LoopBack 3.0, the Bluebird API is considered a part of the LoopBack API. You can use any Bluebird-specific methods in your applications.

For more information, see loopback#1896 and loopback-datasource-juggler#790.

For more information, see how to migrate

New SOAP connector

In LoopBack 3.0, the loopback-soap-connector uses the new strong-soap module instead of node-soap. strong-soap provides a SOAP client for invoking web services, APIs to convert JSON to XML, XML to JSON, parse XML, load WSDL, and so on. It provides comprehensive support for different types of WSDLs and XSDs and a mock-up SOAP server capability to create and test your web service.

For more information, see the strong-soap README.

Changes to LoopBack Explorer

Version 2.x used a fork of Swagger UI for LoopBack Explorer (loopback-component-explorer). While the fork added several enhancements to the original project, we were not able to keep it up to date with upstream changes. As a result, several important fixes and improvements made upstream were not available to Explorer users.

LoopBack 3.0 reverts back to using the swagger-ui module.

For more information, see:

Advanced

This section describes changes that affect only advanced use of LoopBack and its internal components. Instructions described in this section are not applicable to typical LoopBack applications.

loopback.autoAttach() removed

Version 3.0 removes the loopback.autoAttach() method and the defaultForType datasource option. Instead of these, use loopback-boot. For more information, see loopback#1989.

Alternatively, modify your code to explicitly attach all models to appropriate datasources. For example:

var MyModel = app.registry.createModel(
  'MyModel',
  {/* properties */},
  {/* options */});
app.model(MyModel, { dataSource: 'db' });

DataSource.registerType() was removed

Version 3.0 removes the DataSource.registerType() method, as it was a deprecated wrapper for ModelBuilder.registerType(). Now use ModelBuilder.registerType directly.

For more information, see loopback-datasource-juggler#976.

Type converters replace Dynamic API

In version 3.0, the Dynamic component was replaced by type converters and a type registry.

Specifically, the following methods were removed:

RemoteObjects.convert(name, fn)
remoteObjectsInstance.convert(name, fn)
RemoteObjects.defineType(name, fn)
remoteObjectsInstance.defineType(name, fn)

Two new methods are added as a replacement:

remoteObjectsInstance.defineType(name, converter)
remoteObjectsInstance.defineObjectType(name, factoryFn)

For more information, see the API docs and pull request #343 for more details.

Change.handleError was removed

Change.handleError() was removed, as it was used inconsistently for a subset of possible errors only. All Change methods now report errors via the callback function.

To report change-tracking errors, use [PersistedModel.handleChangeError](http://apidocs.loopback.io/loopback/#persistedmodel-handlechangeerror). You can customize this method to provide custom error handling for a model.

For more information, see loopback#2308.

app.model(modelName, settings) was removed

Version 3.0 removes the method app.model(modelName, settings). Instead use:

  • app.registry.createModel(modelName, properties, options) to create new model.
  • app.model(modelCtor, config) to update existing model and attach it to app.

For more information, see loopback#2401.