See also:
- Migrating apps to v3
- Change log (latest updates)
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
anddetails
. - All other errors (including non-Error values like strings or arrays) provide
only basic information:
statusCode
andmessage
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:
- How to migrate (new REST error handler)
strong-error-handler
.- Environment-specific configuration
- Rationale behind the new handler
- strong-remoting PR #302
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 topatchAttributes
.updateOrCreate
was renamed topatchOrCreate
.
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).
-
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.
-
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.
Note:
This change does not affect LoopBack applications scaffolded by a recent version of
StrongLoop/LoopBack tools, as these applications don’t rely on the built-in CORS middleware and configure CORS explicitly in server/middleware.json
.
For more information, see:
- Strong-remoting pull request #352 and Security considerations.
- How to migrate.
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.