Code Monkey home page Code Monkey logo

loopback-component-jsonapi's Introduction

loopback-component-jsonapi

Greenkeeper badge

Join the chat at https://gitter.im/digitalsadhu/loopback-component-jsonapi Build Status npm version Dependency Status devDependency Status Coverage Status

jsonapi.org support for loopback.

Status

This project is now pretty stable and is used in production in a number of our projects. There are known issues (see below and the issue tracker) these can mostly be worked around or are pretty minor. Open an issue on the issue tracker if you need clarification on anything or need help.

Known issues

This module doesn't do complex compound documents very well yet. This means that if you try to do complex includes in a single request you will likely run into trouble.

We wrote another module called loopback-jsonapi-model-serializer that does JSONAPI serialization very well (but nothing else) for loopback which you can use to get around such issues for now. The long term goal is to swap out the serialization layer in loopback-component-jsonapi with loopback-jsonapi-model-serializer

Tested against:

  • Node 4, 6 and 8
  • JSON API v1.0
  • loopback ^3.8.0

Sample Project

We have created a sample project using EmberJS, Loopback and this compoment. It's called emberloop.

Helping out

We are VERY interested in help. Get in touch via the issue tracker Please read the following about contributing:

Semantic Release

This project uses Semantic Release to manage the release process. This means that: A. There is no semver project version in package.json. This is managed in CI. B. Commit messages need to follow conventions. See here for commit message guidelines. The important things to remember are: A. If you are fixing a bug prefix your commit message with fix(<thing being fixed goes here>): B. If you are adding a non breaking feature, prefix your commit with feat(<name of feature goes here>): C. If you are making a breaking change of any kind, prefix additional information on the 3rd line of the commit message with: BREAKING CHANGE: See examples of this on the Semantic Release github pages. And don't hesitate to reach out on our issue tracker if you want further clarification.

Standard js and "prettier standard"

This project is follows the Standard js styleguide. Linting happens on CI and any time you run tests via npm test You can run the linting on its own with npm run lint

Additionally, code formatting is done whenever you run git commit. This is made possibly by lint-staged and husky with actual formatting done by prettier

Pull requests and code review

All code is reviewed by one or more of the project maintainers before merging. Before becoming a maintainer, contributers need to fork the master branch of this repo, make their changes and submit a pull request.

Once a contributor becomes a maintainer, it is preferred that they create new branches on the loopback-component-jsonapi repo and submit those as pull requests

Tests

We take testing seriously. The project contains over 200 tests at time of writing this. In most cases we wont merge anything without tests. (Within reason of course)

Project maintainers

We follow the principle of "Open open source" which means if you contribute even a single PR to the project, we make you a project maintainer.

Debugging

You can enable debug logging by setting an environment variable: DEBUG=loopback-component-jsonapi

example:

DEBUG=loopback-component-jsonapi node .

API Documentation

Getting started

In your loopback project:

  1. npm install --save loopback-component-jsonapi
  2. Create a component-config.json file in your server folder (if you don't already have one)
  3. Add the following config to component-config.json
{
  "loopback-component-jsonapi": {}
}

Advanced usage:

We are aiming to make the component as configurable as possible. You can configure how the component behaves with the options shown and listed below. If there is something else you would like to see be configurable, please submit an issue on the repository. For remote methods, root must be set to true.

Example: (all configuration options listed)

{
  "loopback-component-jsonapi": {
    "restApiRoot": "/api",
    "host": "https://www.mydomain.com",
    "enable": true,
    "handleErrors": true,
    "errorStackInResponse": false,
    "handleCustomRemoteMethods": false,
    "exclude": [
      {"model": "comment"},
      {"methods": "find"},
      {"model": "post", "methods": "find"},
      {"model": "person", "methods": ["find", "create"]}
    ],
    "hideIrrelevantMethods": true,
    "attributes": {
      "posts": ["title"]
    },
    "include": [
      {"methods": "customMethod"},
      {"model": "post", "methods": "customMethod"},
      {"model": "person", "methods": ["customMethod1", "customMethod2"]}
    ]
  }
}

restApiRoot

Url prefix to be used in conjunction with host and resource paths. eg. http://127.0.0.1:3214/api/people

example

{
  ...
  "restApiRoot": "/api",
  ...
}
  • Type: string
  • Default: /api

host

The url of the application, to be used when constructing links to relationships. Useful where the service is proxied and the application believes it is running on a different url to that seen by the consuming service.

example

{
  ...
  "host": "https://www.mydomain.com",
  ...
}
  • Type: string
  • Default: null

enable

Whether the component should be enabled or disabled. Defaults to true, flip it to false if you need to turn the component off without removing the configuration for some reason.

example

{
  ...
  "enable": true,
  ...
}
  • Type: boolean
  • Default: true

handleErrors

When true, the component will unregister all other error handling and register a custom error handler which always returns errors in JSON API compliant format. Validation errors include the correct properties in order to work out of the box with EmberJS.

example

{
  ...
  "handleErrors": true,
  ...
}
  • Type: boolean
  • Default: true

errorStackInResponse

Along handleErrors, When true, this option will send the error stack if available within the error response. It will be stored under the source.stack key.

Please be careful, this option should never be enabled in a production environment. Doing so can expose sensitive data.

example

{
  ...
  "errorStackInResponse": NODE_ENV === 'development',
  ...
}
  • Type: boolean
  • Default: false

handleCustomRemoteMethods

Allow all (custom) remote methods to be serialized by default.

This option can be overridden in any of the following ways:

  1. Setting a jsonapi property to true or false in a remote method definition.
  2. Globally adding the remote method to the component's exclude array.
  3. Globally adding the remote method to the component's include array.

example

{
  ...
  "handleCustomRemoteMethods": true,
  ...
}
  • Type: boolean
  • Default: false

exclude

Allows blacklisting of models and methods. Define an array of blacklist objects. Blacklist objects can contain "model" key "methods" key or both. If just "model" is defined then all methods for the specified model will not be serialized of deserialized using JSON API. If just the "methods" key is defined then all methods specified on all models will not be serialized or deserialized using JSON API. If a combination of "model" and "methods" keys are used then the specific combination of model and methods specified will not be serialized or deserialized using JSON API.

example

{
  ...
  "exclude": [
    {"model": "comment"},
    {"methods": "find"},
    {"model": "post", "methods": "find"},
    {"model": "person", "methods": ["find", "create"]}
  ],
  ...
}
  • Type: array
  • Default: null

Note

The default behavior is to modify (serialize to JSON API) the output of the following CRUD methods on all models:

  • find
  • create
  • updateAttributes
  • deleteById
  • findById

In addition the following wild card method names are matched and the output is modified in order to handle relationships eg. /api/posts/1/comments

  • __get__.*
  • __findRelationships__.*

The default behavior is to modify (deserialize from JSON API) the input to the following CRUD methods on all models:

  • create
  • updateAttributes

include

Allows whitelisting of methods. Define an array of whitelist objects. Whitelist objects can contain a "methods" key or both a "models" key and a "methods" key. If just the "methods" key is defined then the methods specified will be serialized or deserialized using JSON API on all models that have the specified methods. If a combination of "model" and "methods" keys are used then the specific combination of model and methods specified will be serialized or deserialized using JSON API.

Note: objects returned from a remote method that will be JSON API serialized MUST include an id property. id property can be null.

example

{
  ...
  "include": [
    {"methods": "customMethod"},
    {"model": "post", "methods": "customMethod"},
    {"model": "person", "methods": ["customMethod1", "customMethod2"]}
  ],
  ...
}
  • Type: array
  • Default: null

hideIrrelevantMethods

By default, loopback-component-jsonapi disables a number of methods from each endpoint that are not JSON API relevant. These methods are:

  • upsert
  • exists
  • findOne
  • count
  • createChangeStream
  • updateAll

You can use this option to prevent loopback-component-jsonapi from doing so. These methods are not modified by the component. Their output will not be in a JSON API compliant format.

example

{
  ...
  "hideIrrelevantMethods": true,
  ...
}
  • Type: boolean
  • Default: true

attributes

By default, model properties will be converted to attributes in JSON API terms. All model properties except the primary key and any foreign keys will be copied into the attributes object before output. If you wish to limit which properties will be output as attributes you can specify a whitelist of attributes for each type.

example

{
  ...
  "attributes": {
    "posts": ["title", "content"],
    "comments": ["createdAt", "updatedAt", "comment"]
  }
  ...
}
  • Type: object
  • Default: null

note

The attributes arrays are keyed by type not by model name. Type is the term used by JSON API to describe the resource type in question and while not required by JSON API it is usually plural. In loopback-component-jsonapi it is whatever the models plural is set to in model.json. So in our example above we defined: "posts": ["title", "content"] as the resource type for the post model is posts

foreignKeys

Allows configuration of whether the component should expose foreign keys (which the jsonapi spec considers implementation details) from the attributes hash.

examples

Always expose foreign keys for all models

{
  ...
  foreignKeys: true,
  ...
}

Never expose foreign keys for any models (default behaviour)

{
  ...
  foreignKeys: false,
  ...
}

Only expose foreign keys for the comment model

{
  ...
  foreignKeys: [
    {model: 'comment'}
  ],
  ...
}

Only expose foreign keys for the comment model findById method. eg. GET /api/comments/1

{
  ...
  foreignKeys: [
    {model: 'comment', method: 'findById'}
  ],
  ...
}
  • Type: boolean|array
  • Default: false

Custom remote methods

jsonapi remote method options

Sometimes you need to be able to control when a custom remote method should be handled by the component. By default, loopback-component-jsonapi will not handle (serialize or deserialize) custom remote methods. In order to tell the component to handle a custom remote method, you have the following options (In priority order):

  1. Set jsonapi to true when defining a custom remote method.
  2. Add the methods name to the component's exclude array setting. (see above)
  3. Add the methods name to the component's include array setting. (see above)
  4. Set handleCustomRemoteMethods to true in the component's settings. (see above)

This option takes precedence and sets the component to handle or not handle the custom remote method.

examples

Post.remoteMethod('greet', {
  jsonapi: true
  returns: { root: true }
})

Ensures that the response from Post.greet will follow JSONApi format.

Post.remoteMethod('greet', {
  jsonapi: false
  returns: { arg: 'greeting', type: 'string' }
})

Ensures that the response from Post.greet will never follow JSONApi format.

Note

You must always pass root: true to the returns object when using loopback-component-jsonapi. This is especialy important when you expect the response to be an array.

Overriding serialization type

When loopback-component-jsonapi serializes a custom remote method, by default it will assume that the data being serialized is of the same type as the model the custom remote method is being defined on. Eg. For a remote method on a Comment model, it will be assumed that the data being returned from the remote method will be a comment or an array of comments. When this is not the case, you will need to set the type property in the returns object in the remote method definition.

If an unknown type or no type are given, the model name will be used.

example

Post.remoteMethod('prototype.ownComments', {
  jsonapi: true
  returns: { root: true, type: 'comment' }
})

Custom Serialization

For occasions where you need greater control over the serialization process, you can implement a custom serialization function for each model as needed. This function will be used instead of the regular serialization process.

example

module.exports = function (MyModel) {
  MyModel.jsonApiSerialize = function (options, callback) {
    // either return an error
    var err = new Error('Unable to serialize record');
    err.status = 500;
    cb(err)

    // or return serialized records
    if (Array.isArray(options.records)) {
      // serialize an array of records
    } else {
      // serialize a single record
    }
    cb(null, options);
  }
}
function parameters
  • options All config options set for the serialization process.
  • callback Callback to call with error or serialized records

Custom Deserialization

For occasions where you need greater control over the deserialization process, you can implement a custom deserialization function for each model as needed. This function will be used instead of the regular deserialization process.

example

module.exports = function (MyModel) {
  MyModel.jsonApiDeserialize = function (options, callback) {
    // either return an error
    var err = new Error('Unable to deserialize record');
    err.status = 500;
    cb(err)

    // or
    // options.data is the raw data
    // options.result needs to be populated with deserialization result
    options.result = options.data.data.attributes;

    cb(null, options);
  }
}

Custom Errors

Generic errors respond with a 500, but sometimes you want to have a better control over the error that is returned to the client, taking advantages of fields provided by JSONApi.

It is recommended that you extend the base Error constructor before throwing errors. Eg. BadRequestError

meta and source fields needs to be objects.

example

module.exports = function (MyModel) {
  MyModel.find = function () {
    var err = new Error('April 1st, 1998');
    
    err.status = 418;
    err.name = 'I\'m a teapot';
    err.source = { model: 'Post', method: 'find' };
    err.detail = 'April 1st, 1998';
    err.code = 'i\'m a teapot';
    err.meta = { rfc: 'RFC2324' };

    throw err
  }
}

// This will be returned as :
// {
//   errors: [
//     {
//       status: 418,
//       meta: { rfc: 'RFC2324' },
//       code: 'i\'m a teapot',
//       detail: 'April 1st, 1998',
//       title: 'I\'m a teapot',
//       source: { model: 'Post', method: 'find' }
//     }
//   ]
// }
function parameters
  • options All config options set for the deserialization process.
  • callback Callback to call with error or serialized records

The options object

options.type

Resource type. Originally calculated from a models plural. Is used in the default serialization process to set the type property for each model in a JSON API response.

  • eg. posts
options.method

The method that was called to get the data for the current request. This is not used in the serialization process but is provided for custom hook and serialization context.

  • Eg. create, updateAttributes
options.primaryKeyField

The name of the property that is the primary key for the model. This is usually just id unless defined differently in a model.json file.

options.requestedIncludes

The relationships that the user has requested be side loaded with the request. For example, for the request GET /api/posts?include=comments options.requestedIncludes would be 'comments'.

  • Type: string or array
  • eg: 'comments' or ['posts', 'comments']
options.host

The host part of the url including any port information.

  • eg. http://localhost:3000
options.restApiRoot

The api prefix used before resource information. Can be used in conjunction with options.host and options.type to build up the full url for a resource.

  • eg. /api
options.topLevelLinks

Links object used at the top level of the JSON API response structure.

  • eg. {links: {self: 'http://localhost:3000/api/posts'}}
options.dataLinks

Links object used to generate links for individual resource items. The structure is and object with JSON API link keys such as self or related that are defined as a function that will be called for each resource.

Eg.

options.dataLinks: {
  self: function (resource) {
    return 'http://localhost:3000/posts/' + resource.id;
  }
}

As shown above, each resource gets passed to the function and the result of the function is assigned to the key in the final JSON API response.

options.relationships

This contains all the relationship definitions for the model being serialized. Relationship definition objects are in the same format as in loopback's Model.relations definition. An object with relationship name keys, each having properties:

  • modelTo loopback model object
  • keyTo name of key on to model
  • modelFrom loopback model object
  • keyFrom name of key on from model
  • type type of relationship (belongsTo, hasOne, hasMany)

This information is used to build relationship urls and even setup side-loaded data correctly during the serialization process.

eg.

options.relationships = {
  comments: { modelTo: ...etc },
  tags: { modelTo: ...etc }
}
options.results

This is the actual data to be serialized. In beforeJsonApiSerialize and jsonApiSerialize this will be the raw data as you would ordinarily get it from loopback. In afterJsonApiSerialize this will be the serialized data ready for any final modifications.

options.exclude

This is the exclude settings as defined in the exclude configuration option explained earlier. Use this in beforeJsonApiSerialize to make any model specific adjustments before serialization.

options.attributes

This is the attributes settings as defined in the attributes configuration option explained earlier. Use this in beforeJsonApiSerialize to make any model specific adjustments before serialization.

options.data

The raw body data prior to deserialization from creates and updates. This can be manipulated prior to deserialization using beforeJsonApiDeserialize

options.result

The deserialized raw body data. This is used when saving models as part of a create or update operation. You can manipulate this prior to the save occuring in afterJsonApiDeserialize

Serialization/Deserialization Hooks

For occasions when you don't want to fully implement (de)serialization for a model manually but you need to manipulate the serialization/deserialization process, you can use the hooks beforeJsonApiSerialize, afterJsonApiSerialize, beforeJsonApiDeserialize and afterJsonApiDeserialize.

beforeJsonApiDeserialize

In order to modify the deserialization process on a model by model basis, you can define a Model.beforeJsonApiDeserialize function as shown below. The function will be called with an options object and a callback which must be called with either an error as the first argument or the modified options object as the second parameter.

Examples of things you might want to use this feature for

  • modifying options.data.data.attributes prior to their being deserialized into model properties that will be saved
  • modifying options.data.data.relationships prior to their being used to save relationship linkages

code example

module.exports = function (MyModel) {
  MyModel.beforeJsonApiDeserialize = function (options, callback) {
    // either return an error
    var err = new Error('Unwilling to deserialize record');
    err.status = 500;
    callback(err)

    // or return modified data
    options.data.data.attributes.title = 'modified title';

    // returned options.data will be deserialized by either the default deserialization process
    // or by a custom deserialize function if one is present on the model.
    callback(null, options);
  }
}

afterJsonApiDeserialize

This function will be called with an options object and a callback which must be called with either an error as the first argument or the modified options object as the second parameter.

Examples of things you might want to use this feature for

  • modifying options.result after their having being deserialized from options.data.data.attributes
  • modifying options.data.data.relationships prior to their being used to save relationship linkages

code example

module.exports = function (MyModel) {
  MyModel.afterJsonApiDeserialize = function (options, callback) {
    // either return an error
    var err = new Error('something went wrong!');
    err.status = 500;
    callback(err)

    // or return modified data prior to model being saved with options.result
    options.result.title = 'modified title';

    callback(null, options);
  }
}
function parameters
  • options All config options set for the deserialization process. See the "the options object" section above for info on what options properties are available for modification.
  • callback Callback to call with error or options object.

beforeJsonApiSerialize

In order to modify the serialization process on a model by model basis, you can define a Model.beforeJsonApiSerialize function as shown below. The function will be called with an options object and a callback which must be called with either an error as the first argument or the modified options object as the second parameter.

Examples of things you might want to use this feature for

  • modify the record(s) before serialization by modifying options.results
  • modify the resource type by modifying options.type
  • setup serialization differently depending on options.method
  • side load data (advanced)
  • modify the way relationships are serialized

code example

module.exports = function (MyModel) {
  MyModel.beforeJsonApiSerialize = function (options, callback) {
    // either return an error
    var err = new Error('Unable to serialize record');
    err.status = 500;
    callback(err)

    // or return modified records
    if (Array.isArray(options.results)) {
      // modify an array of records
    } else {
      // modify a single record
    }
    // returned options.records will be serialized by either the default serialization process
    // or by a custom serialize function (described above) if one is present on the model.
    callback(null, options);
  }
}
function parameters
  • options All config options set for the serialization process. See the "function parameters" section above for info on what options properties are available for modification.
  • callback Callback to call with error or options object.

example use case

Because the beforeJsonApiSerialize method is passed all the options that will be used during serialization, it is possible to tweak options to affect the serialization process. One example of this is modifying the type option to change the resource type that will be output.

module.exports = function (MyModel) {
  MyModel.beforeJsonApiSerialize = function (options, callback) {
    options.type = 'mycustommodels';
    cb(null, options);
  }
}

afterJsonApiSerialize

In order to modify the serialized data on a model by model basis, you can define a Model.afterJsonApiSerialize function as shown below. The function will be called with an options object and a callback which must be called with either an error as the first argument or the modified options object as the second parameter.

example

module.exports = function (MyModel) {
  MyModel.afterJsonApiSerialize = function (options, callback) {
    // either return an error
    var err = new Error('Unable to modify serialized record');
    err.status = 500;
    callback(err)

    // or return modified records
    if (Array.isArray(options.results)) {
      // modify an array of serialized records
    } else {
      // modify a single serialized record
    }
    // returned options.records will be output through the api.
    callback(null, options);
  }
}
function parameters
  • options All config options set for the serialization process
  • callback Callback to call with modified serialized records

loopback-component-jsonapi's People

Contributors

alltouch avatar anotheredward avatar benjaminhorn avatar digitalsadhu avatar gitter-badger avatar greenkeeper[bot] avatar greenkeeperio-bot avatar hamzahio avatar jonathanprince avatar jonforest avatar listepo avatar markstuart avatar nfrasser avatar petitchevalroux avatar resonance1584 avatar rocknrolla777 avatar ryanxwelch avatar takdw avatar taras avatar tronix117 avatar tsteuwer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

loopback-component-jsonapi's Issues

Improve error handling code

I have submitted a PR to strong-remoting to allow us to handle all errors in a single error handler. This should allow us to clean up the code dramatically.

See: strongloop/strong-remoting#248

Once the PR is accepted, we need to use:

app.remotes().options.rest.handleErrors = false;

To disable the RestAdapter errorhandler and we can rely on our error handler middleware to catch all errors from then on.

We can also drop afterError handling as its not needed

Checklist:

  • remove duplicated error handling
  • generalise error codes for validation errors eg. 400 with array of validation errors with 422 status code.
  • clean up code
  • create separate errors test file, expand errors testing
  • expand tests

Can't POST when model has relationship that isn't set, even though it's not required in loopback model

So, in loopback, I made relationships where a Person can have an Address" (required: false). However, if I want to create thePersonwithout theAddress`, I can't.

POST Request

data: {
   attributes: {
      first-name: "Joe",
      last-name: "Shmoe",
      age: 31,
      created-at: "2015-10-31T14:24:02.918Z"
   },
   relationships: {
      address: {
         data: null
      }
   }
   type: "people"
}

Response

{ 
   errors: [
      {
         status: 500,
         detail: "Cannot read property 'type' of null"
      }
     ]
}

What I believe is happening is it's trying to add the relationship, even though the data is null (aka, an empty relationship as the loopback model does not require it).

406 Not Acceptable

It looks like we are not doing something correctly. The JSON API spec is shows:

Servers MUST respond with a 406 Not Acceptable status code if a request's Accept header contains the JSON API media type and all instances of that media type are modified with media type parameters.

However, when sending an actual POST message with Accept: application/vnd.api+json, the component is responding with a 406 Not Acceptable.

Change all tests to use JSON API Content-Type

Now that I think supertest/superagent has added support for application/vnd.api+json,
(see ladjs/superagent#753)
we should be able to modify all our tests to use Content-Type: application/vnd.api+json instead of application/json

This would be ideal if at a later stage we want to try to support switching how loopback handles input using the Content-Type header.

Remote Methods

How about converting remote methods result to jsonapi?

include a Location header identifying the location of the newly created resource.

Info from JSON API

The response SHOULD include a Location header identifying the location of the newly created resource.

The response MUST also include a document that contains the primary resource created.

If the resource object returned by the response contains a self key in its links member and a Location header is provided, the value of the self member MUST match the value of the Location header.

eg.

HTTP/1.1 201 Created
Location: http://example.com/photos/550e8400-e29b-41d4-a716-446655440000
Content-Type: application/vnd.api+json

{
  "data": {
    "type": "photos",
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "attributes": {
      "title": "Ember Hamster",
      "src": "http://example.com/images/productivity.png"
    },
    "links": {
      "self": "http://example.com/photos/550e8400-e29b-41d4-a716-446655440000"
    }
  }
}

Loopback vs JSONAPI filter syntax

Description

Loopback provides the necessary syntax for filtering results.

JSON API is agnostic about how filtering is implemented and only specifies that the filter key is used. In some ways, since loopback does use the filter key, the implementation is already correct though perhaps not ideal.

Loopback example:

https://docs.strongloop.com/display/public/LB/Where+filter

filter[where][property]=value
filter[where][property][op]=value

Note the use of the where namespace which, while not violating anything in JSON API, could be removed for a better API.

JSON API example

http://jsonapi.org/format/#fetching-filtering

The filter query parameter is reserved for filtering data. Servers and clients SHOULD use this key for filtering operations.
Note: JSON API is agnostic about the strategies supported by a server. The filter query parameter can be used as the basis for any number of filtering strategies.

The following is how I would expect an ideal solution to work:

filter[property]=value
filter[property][op]=value

Also important to note is that it may cause us issues to use the filter key in a non standard way since all the url based operations in loopback use this key. Eg. Offset, limit, where, fields. It may simply be easier to treat this as technically compliant and leave it at that in the short term.

Improve scoping of usage

I have been experiencing pain using the component where the regex matches we use to determine where to apply the component to api output and where not is a bit loose.

Eg.
Defining a remoteMethod with the name 'createMyThing' gets treated as 'create' and so the component is applied to the output of the remote method.

Relationships

Description

This is the big feature we still need to support.
The current way to support these relationships is going to have to be to overwrite the remoting relationship methods in the Model class. (loopback repo, lib/model.js)

  • hasOneRemoting
  • hasManyRemoting
  • belongsToRemoting
  • hasManyThroughRemoting <-- will not be supported in first v1 (enough work to do already and this ones going to be hard)

This isn't ideal as it's reaching pretty far into the loopback internals. Plan should be to get it done this way and then run it past the strongloop guys eg. @bajtos to see if a better way can be found to do this

Explanation

Has One (PATCH ONLY)

Replace the existing resource with PATCH as follows

PATCH /articles/1/relationships/author HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": { "type": "people", "id": "12" }
}

Remove the existing resource with PATCH as follows

PATCH /articles/1/relationships/author HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": null
}

Has Many (PATCH, POST, DELETE)

We need to replace all relationships for a specific key with PATCH

Eg. The following completely replaces all tags for an article

PATCH /articles/1/relationships/tags HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": [
    { "type": "tags", "id": "2" },
    { "type": "tags", "id": "3" }
  ]
}

We need to remove all related resources for PATCH

Eg. The following completely removes all tags for an article

PATCH /articles/1/relationships/tags HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": []
}

We need to append with POST

Eg. The following will append comment 124 to the comments for article 1

POST /articles/1/relationships/comments HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": [
    { "type": "comments", "id": "123" }
  ]
}

We need to delete with a DELETE

Eg. The following removes comments 12 and 13 only from articles 1

DELETE /articles/1/relationships/comments HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": [
    { "type": "comments", "id": "12" },
    { "type": "comments", "id": "13" }
  ]
}

TODO

  • Expand test coverage to cover items below

GET /:collection/:id/:relatedCollection

  • hasMany
  • hasOne
  • belongsTo

GET /:collection/:id/relationships/:relatedCollection

  • hasMany
  • hasOne
  • belongsTo

POST /:collection/:id/relationships/:relatedCollection

  • hasMany
  • hasOne
  • belongsTo

PATCH /:collection/:id/relationships/:relatedCollection

  • hasMany
  • hasOne
  • belongsTo

DELETE /:collection/:id/relationships/:relatedCollection

  • hasMany
  • hasOne
  • belongsTo

wrong self link

http://localhost:3000/api/services returns. The "self" link does not represent the item's id.

{
  "data": [
    {
      "type": "services",
      "relationships": {
        "category": {
          "data": null
        }
      },
      "id": "1",
      "attributes": {
        "name": "Service 1"
      },
      "links": {
        "self": "http://localhost:3000/api/services/1"
      }
    },
    {
      "type": "services",
      "relationships": {
        "category": {
          "data": null
        }
      },
      "id": "2",
      "attributes": {
        "name": "Service 2"
      },
      "links": {
        "self": "http://localhost:3000/api/services/1"
      }
    }
  ],
  "links": {
    "self": "http://localhost:3000/api/services"
  }
}

belongsTo through hasMany

I have the following models:

// model/rule.json
{
  "name": "Rule",
  ...
  "postgresql": {
    "schema": "public",
    "table": "rule"
  },
  "include": ["Action"],
  "properties": {
  ...
  },

  "relations": {
  ...
    "actions": {
      "type": "hasMany",
      "model": "Action",
      "foreignKey": "ruleId"
    },
  ...
  },
  "acls": [],
  "methods": {}
}

// model/action.json
{
  "name": "Action",
  ...
  "properties": {
   ...
    "cardId": {
      "type": "Number",
      "required": false,
      "index": true,
      "length": null,
      "precision": 32,
      "scale": 0,
      "postgresql": {
        "columnName": "card_id",
        "dataType": "integer",
        "dataLength": null,
        "dataPrecision": 32,
        "dataScale": 0,
        "nullable": "YES"
      },
      "_selectable": true
    }
  },
  "validations": [],
  "relations": {
    "card": {
      "type": "belongsTo",
      "model": "Card",
      "foreignKey": "cardId"
    }
  },
..
}

// models/card.json

{
  "name": "Card",
        ...
  "properties": {
        ...
  },
  "validations": [],
  "relations": {
    "galleries": {
        ...
    },
    "tags": {
        ...
    },
    "actions": {
      "type": "hasMany",
      "model": "Action",
      "foreignKey": "cardId"
    }

  },
  "acls": [],
  "methods": {}
}

In Ember I try to access a rule, loop through its actions, and show each action's card

Something like this:
http://jsbin.com/taqatuzagu/1/edit?html,js,output

The problem:
If I go to localhost:300/rules/1/actions it does not show any information about the card relationship. the object holds the rule's relationships.

{
  "data": [
    {
      "type": "actions",
      "relationships": {
        ...
        "actions": {
          "links": {
            "related": "http://localhost.hu:3000/api/Rules/1/actions"
          }
        },
        ...
      },
      "id": "1",
      "attributes": {
        "name": "welcome",
        "url": "",
        "type": "card",
        "json": "",
        "card-id": 2
      },
      "links": {
        "self": "http://localhost.hu:3000/api/Actions/1"
      }
    },
    {
      "type": "actions",
      "relationships": {
        ...
        "actions": {
          "links": {
            "related": "http://localhost.hu:3000/api/Rules/3/actions"
          }
        }
        ...
      },
      "id": "3",
      "attributes": {
        "name": "second",
        "url": "",
        "type": "summary",
        "json": "",
        "card-id": 2
      },
      "links": {
        "self": "http://localhost.hu:3000/api/Actions/1"
      }
    }
  ],
  "links": {
    "self": "http://localhost.hu:3000/api/Rules/1/actions"
  }
}

Add json api content type as an option in loopback explorer

Currently its not possible to specify the accepts content type for json api in the loopback explorer.
Its not currently possible to do this in a boot script or component but it is possible to do so globally. @bajtos has offered to land a patch in loopback to make this possible in a component/boot script which would help. See here:

strongloop/loopback#445 (comment)

ATM, it's not possible to customise the list of content types at route (method) level - see https://github.com/strongloop/loopback-swagger/blob/8eb73acbaae2cd600d03a95dcf55aee6f7560980/lib/specgen/route-helper.js#L161-L162.

However, you can customise this list at global level via options.consumes and options.produces passed to loopback-component-explorer, see https://github.com/strongloop/loopback-swagger/blob/8eb73acbaae2cd600d03a95dcf55aee6f7560980/lib/specgen/swagger-spec-generator.js#L22-L36

Add loopback feature regression tests

Theres a risk that as we modify the way loopback accepts and returns data to match the JSON API spec, we may break things that we aren't aware of. It would be great if someone could find the time to add some basic tests to cover various features:

It should be pretty straight forward to create the tests, just refer to the tests we have all ready in the test folder.

Some areas that could use testing are:

  • referencesMany relationships
  • embedsOne, embedsMany etc
  • the built in user model methods like login and logout
  • etc

Publishing releases

@listepo @tsteuwer
I wanted to get some feedback on how we publish releases:

Currently, I'm not entirely following semver as the project still has a lot of work to do before I think its ready for a 1.0 release. I think that until 1.0 we should treat the minor number as major and the patch number as minor and patch. Once we hit 1.0, we can start following semver properly.

Eg.
Before 1.0.0

  • Breaking change, current version 0.4.0 goes to 0.5.0
  • Feature addition, current version 0.4.0 goes to 0.5.1
  • Bug fixes, current version 0.4.0 goes to 0.5.1

After 1.0.0

  • Breaking change, current version 1.0.0 goes to 2.0.0
  • Feature addition, current version 1.0.0 goes to 1.1.0
  • Bug fixes, current version 1.0.0 goes to 1.0.1

Content-Type inconsistencies

Some responses are coming back from the server with:

Content-Type: application/vnd.api+json; charset=utf-8

Others just:

Content-Type: application/vnd.api+json

Review basic (no relations) JSON API methods and fix as needed

Basic Fetch

GET /models

  • correct accept headers (application/vnd.api+json)
  • correct content type headers (application/vnd.api+json)
  • correct status codes (200 OK)
  • correct data format
    • data
    • self link
    • empty array returned for empty collection

GET /models/{id}

  • correct accept headers (application/vnd.api+json)
  • correct content type headers (application/vnd.api+json)
  • correct status codes
    • 404 returned if resource doesn't exist
    • 200 returned if resource exists
  • correct data format
    • data
    • self link

Basic create

POST /models

  • correct accept headers (application/vnd.api+json)
  • correct content type headers (application/vnd.api+json)
  • correct status codes
    • 201 Created
  • Location header present after create (same url as self)
  • correct data format returned
    • data
    • self link
    • data.type field present
  • correct formatted errors object returned

Basic update

PATCH /models/{id}

  • correct accept headers (application/vnd.api+json)
  • correct content type headers (application/vnd.api+json)
  • correct status codes
    • 200 OK
    • 404 Not found
  • correct data format returned
    • data
      • type field present
      • id field present
      • any attributes present but not compulsory
    • self link
  • correct formatted errors object returned

Basic delete

DELETE /models/{id}

  • correct accept headers (application/vnd.api+json)
  • correct content type headers (application/vnd.api+json)
  • correct status codes
    • 204 No Content

Submit PR to strong-remoting to add support for application/vnd.api+json

Should be a very simple change. Just need to add a case to switch statement that falls through to json.

See discussion here:
strongloop/loopback#445 (comment)

TODO:

  • Make sure components loopback/strong-remoting dependency up to v2.22.1
  • Remove explicit setting of Content-Type: application/vnd.api+json in various places in the component
  • Add README documentation that it is necessary to set Accept: application/vnd.api+json when using this component and that doing so will result in Content-Type: application/vnd.api+json being set on the response
  • Ensure tests pass
  • Manually test that this works

using include with related urls fails

Example of failing urls:
GET http://localhost:3000/api/posts/9/author?include=posts

{
  "errors": [
    {
      "status": 500,
      "source": "",
      "title": "TypeError",
      "code": "",
      "detail": "Cannot read property 'keyFrom' of undefined"
    }
  ]
}

Whats happening is that everything hinges off the base model, in this case post so loopback thinks you are asking for posts related to posts which is wrong. We want posts related to the posts authors.

required compound attributes

Here is a POST payload from Ember:

{"data":{"attributes":{"name":"foo","major-value":3000},"relationships":{"bars":{"data":[]}},"type":"foos"}}

If majorValue required, it causes

{
  "errors": [
  ...
    {
      "status": 422,
      "source": {
        "pointer": "data/attributes/majorValue"
      },
      "title": "ValidationError",
      "code": "presence",
      "detail": "can't be blank"
    }
  ]
}

If in explorer I change major-value to majorValue by hand, it passes.

Including relationship data when fetching data (aka side loading)

@listepo @tsteuwer
Below is a write up of my understanding of what we need to do to support returning relationship information in GET requests to /:collection and /:collection/:id

Does it look right?


Loopback allows you to control when relationship data is returned with a url filter:

Eg.

  • GET /:collection?filter={"include": "author"}
  • GET /:collection/:id?filter={"include": "author"}

Looking at the JSON API spec:

If an endpoint supports the include parameter and a client supplies it, the server MUST NOT include unrequested resource objects in the included section of the compound document.

The value of the include parameter MUST be a comma-separated (U+002C COMMA, ",") list of relationship paths. A relationship path is a dot-separated (U+002E FULL-STOP, ".") list of relationship names.

If a server is unable to identify a relationship path or does not support inclusion of resources from a path, it MUST respond with 400 Bad Request.

the ideal would be something like:

Eg. no include filter in url
GET http://localhost:3000/api/posts/9

{
  "data": {
    "type": "posts",
    "id": "9",
    "attributes": {
      "title": "test post",
      "content": "ttseas"
    },
    "relationships": {
      "author": {
        "links": {
          "related": "http://localhost:3000/posts/9/author"
        }
      }
    }
    "links": {
      "self": "http://localhost:3000/api/posts/9"
    }
  },
  "links": {
    "self": "http://localhost:3000/api/posts/9"
  }
}

Eg. include filter in url
GET http://localhost:3000/api/posts/9?filter={"include":"author"}

{
  "data": {
    "type": "posts",
    "id": "9",
    "attributes": {
      "title": "test post",
      "content": "ttseas"
    },
    "relationships": {
      "author": {
        "links": {
          "related": "http://localhost:3000/posts/9/author"
        },
        "data": { "type": "people", "id": "1" }
      }
    }
    "links": {
      "self": "http://localhost:3000/api/posts/9"
    }
  },
  "links": {
    "self": "http://localhost:3000/api/posts/9?filter=%7B%22include%22%3A%20%22author%22%7D"
  },
  "included": [{
    "type": "people",
    "id": "1",
    "attributes": {
      "name": "Bob Jones"
    },
    "links": {
      "self": "http://localhost:3000/people/1"
    }
  }]
}
  • add thorough test coverage for items below:
  • include relationships link in GET requests for collections and resources eg. "author": { "links": { "related": "http://localhost:3000/posts/9/author" }}
  • rewrite url &include=author -> filter={"include":"author"} to support spec requirement
  • return 400 error if non existent relationship is included in url eg. &include=idontexist
  • remove FK links out of any attributes objects. Probably only applies to belongsTo relationship

Incorrect relationship information for /:collection/:id/:relatedCollection

Given the following request:

GET /companies/1/projects

I am getting back a collection of projects which is good. However, each project contains a relationships block with a projects object with a links.related of /companies/1/projects see the following:

"projects": {
  "links": {
    "related": "http://localhost:3000/api/companies/1/projects"
  }
}

When the relationship should be pointing back at the companies. Eg:

"companies": {
  "links": {
    "related": "http://localhost:3000/api/projects/1/companies"
  }
}

See full data below:

{
  "data": [
    {
      "type": "projects",
      "relationships": {
        "projects": {
          "links": {
            "related": "http://localhost:3000/api/companies/1/projects"
          }
        }
      },
      "id": "1",
      "attributes": {
        "name": "A project",
        "reference": "My project ref",
        "location": "Oslo",
        "summary": "A big project",
        "created-by": 1
      },
      "links": {
        "self": "http://localhost:3000/api/projects/1"
      }
    }
  ],
  "links": {
    "self": "http://localhost:3000/api/companies/1/projects"
  }
}

I'm secretly hoping this will be fixed by #59 but in case it's not this issue stands.

Code Cleanup

I'm trying to get my hands wet on the back-end side so I can start helping with some of these issues. Some of the code is messy and can easily be cleaned up. Would anybody mind if I took this chance to clean up the code and learn a bit about how things work in this?

null relationship data

in my database

service table

id name service-group-id
1 service1 1

service-group table

id name
1 service-group1

Loopback

service.json

{
  "name": "service",
...
  "include": ["service-group"],
  "properties": {
    "name": {
      "type": "string",
      "required": true
    },
    "service-group-id": {
      "type": "number"
    }
  },
  "relations": {
    "service-group": {
      "type": "belongsTo",
      "model": "service-group",
      "foreignKey": "service-group-id"
    }
  },
...
}

localhost:3000/explorer

http://localhost:3000/api/services

{
  "data": [
    {
      "type": "services",
      "relationships": {
        "service-group": {
          "data": null
        }
      },
      "id": "1",
      "attributes": {
        "name": "service1"
      },
      "links": {
        "self": "http://localhost:3000/api/services/1"
      }
    }
  ]
}

http://localhost:3000/api/services/1/service-group

{
  "data": {
    "type": "service-groups",
    "id": "1",
    "attributes": {
      "name": "service-group1"
    },
    "links": {
      "self": "http://localhost:3000/api/service-groups/1"
    }
  },
  "links": {
    "self": "http://localhost:3000/api/services/1/service-group"
  }
}

Why is service-group.data: null? Should not included some data like this suggests: http://emberjs.com/api/data/classes/DS.JSONAPISerializer.html?

Relation creation and update as part of resource create or update

Another feature we need to support is to be able to create and update relationships as part of creating or updating a resource.

Description

Ie. Given 2 models post and comment where a post can have many comments we need to be able to update the links between post and comment as part of updating or creating the post

Relevant specification information

Creating resources

If a relationship is provided in the relationships member of the resource object, its value MUST be a relationship object with a data member. The value of this key represents the linkage the new resource is to have.

Updating resources

Any or all of a resource's relationships MAY be included in the resource object included in a PATCH request.

If a request does not include all of the relationships for a resource, the server MUST interpret the missing relationships as if they were included with their current values. It MUST NOT interpret them as null or empty values.

If a relationship is provided in the relationships member of a resource object in a PATCH request, its value MUST be a relationship object with a data member. The relationship's value will be replaced with the value specified in this member.

A server MAY reject an attempt to do a full replacement of a to-many relationship. In such a case, the server MUST reject the entire update, and return a 403 Forbidden response.

Note: Since full replacement may be a very dangerous operation, a server may choose to disallow it. For example, a server may reject full replacement if it has not provided the client with the full list of associated objects, and does not want to allow deletion of records the client has not seen.

Examples

Example of creating a post with an author relationship

POST /posts HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "posts",
    "attributes": {
      "title": "Ember Hamster",
      "src": "http://example.com/images/productivity.png"
    },
    "relationships": {
      "author": {
        "data": { "type": "people", "id": "9" }
      }
    }
  }
}

Example of creating a post with related comments

POST /posts HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "posts",
    "attributes": {
      "title": "Ember Hamster",
      "src": "http://example.com/images/productivity.png"
    },
    "relationships": {
      "comments": {
        "data": [
          { "type": "comments", "id": "2" },
          { "type": "comments", "id": "3" }
        ]
      }
    }
  }
}

Example of replacing the author for a post:

PATCH /posts/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "posts",
    "id": "1",
    "relationships": {
      "author": {
        "data": { "type": "people", "id": "1" }
      }
    }
  }
}

Example of replacing all comments for a post:

PATCH /posts/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "posts",
    "id": "1",
    "relationships": {
      "comments": {
        "data": [
          { "type": "comments", "id": "2" },
          { "type": "comments", "id": "3" }
        ]
      }
    }
  }
}

Todo

  • Add tests to cover use cases

Use relationships data from post request body to create relationship links for:

  • belongsTo
  • hasOne
  • hasMany

Use relationships data from patch request body to update relationship links for:

  • belongsTo
  • hasOne
  • hasMany

Improve relationship links

Most relationship links have been implemented however loopbacks default way of supporting linking many to many models is difficult to reconcile with what JSON API expects.

Assuming you had an existing post with id 1 and an existing comment with id 1

in loopback:
PUT /posts/1/comments/rel/1 will link them by creating the relationship table entry

JSON API expects (I think) something like:

PATCH /posts/1/relationships/comments

{
  "data": { "type": "comment", "id": "1" }
}

Currently unsure how to approach this.

Missing port in links urls

I've run into an issue when running the component behind boot2docker on mac. I think whats happening is that the port is not being added to generated links so as long as the app is being accessed via port 80 all is well.

component application scope configuration

There have been cases for us when using the component where we want to do something manual and do not want the component to be applied to the output or PersistedModel methods.

Eg.

  • For a specific model, don't use the component
  • For a specific method (create, updateAttributes etc) of a specific model, don't use the component
  • For a specific method for all models, don't use the component

It should be pretty straight forward to add configuration to component-config.json to handle this

Create tests

Currently there are no unit or integration tests. I consider these necessary for a production release.

Casing issue with relationship links

I'm seeing dash case being used in relationship links in a collection when the actual casing is camel casing.

In loopback

project model

...
"relations": {
    "companyCreatedFor": {
      "type": "belongsTo",
      "model": "company",
      "foreignKey": "companyCreatedForId"
    }
  },
...

Looking at the explorer we have:
GET /projects/{id}/companyCreatedFor
GET /projects/{id}/relationships/companyCreatedFor

But in the data from GET /projects I'm seeing:

"relationships": {
  "company-created-for": {
    "links": {
      "related": "http://localhost:3000/api/projects/2/company-created-for"
    }
  }
},

Sparse Fieldsets

Spare fieldsets is where the data returned from the server can be slimmed down by fields

eg. given the following data fetched using
GET /api/projects/1

{
  "data": {
    "type": "projects",
    "id": 1,
    "attributes": {
      "name": "A project",
      "reference": "My project ref",
      "location": "Oslo",
      "summary": "A big project",
      "companyFor": "my company"
    }
  }
}

spare fieldsets can be used to return only a subset of the fields.
GET /api/projects/1?fields[projects]=name

{
  "data": {
    "type": "projects",
    "id": 1,
    "attributes": {
      "name": "A project"
    }
  }
}

See http://jsonapi.org/format/#fetching-sparse-fieldsets for details in the JSON API spec.

Loopback already provides this functionality in the form of filter fields.
https://docs.strongloop.com/display/public/LB/Fields+filter

Example:

filter[fields][propertyName]=<true|false>&filter[fields][propertyName]=<true|false>...

There are quite substantial differences in what JSON API expects and what loopback provides.
I think it should be possible for us to do some sort of mapping but perhaps some kind of spike into how it might work is necessary.

I consider this fairly low priority for now as we have bigger fish to fry.

Create ember js example project

I haven't had time to do this yet. It would be good to test out current functionality and see how it plays with a modern ember/ember data project.

I think it should be a separate repo and we can maintain it over time to show others how to get up and running with loopback and ember.

loopback's filter param doesn't work with the component.

If you open up the emberloop project and go to 127.0.0.1:4200 and watch it run through the tests, the filter test fails with:

GET /person?filter[firstName]=John (returned 4 instead of returning 1)

The test only creates 1 person object with the firstName of John.

serialize a remote method's response

First of all thanks for this awesome component. Finally Ember and loopback work nicely together.

I have the following issue:

module.exports = function(Foo) {


  Foo.bar = function(id, cb) {
    Foo.findById(id,{}, function(err, base){
      // ...
      Foo.find({}, function(err, foos) {
        foos.forEach(function(foo){
          // ... some logic here ...
        });

        cb(null, foos); // <-- needs some formatting
      });
    });
  }

 Foo.remoteMethod(
      'bar', 
      { 
        accepts: {arg: 'id', type: 'Number', required: true},
        returns: {arg: 'foos'},
        http: {path: '/:id/foo-bar'}
      }
  );
};

The problem, that the playload will have the loopback format, so I can't do

let self = this;
this.get('ajax').call('POST', record.constructor.modelName, record.get('id'), 'foo-bar').then(function(res){
  self.store.pushPayload('foo', res);
}

because res wont have JSON API format.
How should I serialize foos before I send it back in the callback?

Retrieving BelongsTo model overwriting relationship foreign key

When I retrieve models with a belongsTo attribute, the linked foreign key Id is being overwritten in the database. I'm only making GET requests.

My best description of the issue is below, sorry I don't have the familiarity to really explain this well.
I have two models: item and listItem.
'item' has one property - name
'listItem' has one property - qty, and a belongsTo relationship with item
So, the listitem table looks like

id  qty  itemId
10  1    1
11  1    2

When I retrieve all items and listItems it makes the following GET requests
GET http://localhost:3000/api/listitems/10/item
GET http://localhost:3000/api/listitems/11/item
GET http://localhost:3000/api/items
http://localhost:3000/api/listitems

The table now looks like

id  qty  itemId
10  1    2
11  1    2

For the sake of example, if I run 'update listitem set itemId=0;'
And then replay on the /listitems/ XHR requests, it sets that itemId to 2.
E.g.

id  qty  itemId
10  1    0
11  1    0

If I then replay the following XHR
GET http://localhost:3000/api/listitems/10/item
The table updates to

id  qty  itemId
10  1    2
11  1    0

I can see the UPDATE happening in the SQL logs, but am not able to track down in the loopback code the logic that is making it fire.

Inconsistent error responses

404 errors, 500 errors and some other errors are returned under the key:

{
  error: {
    //..
  }
}

This is the default way loopback returns errors and it has proved difficult to change consistently across the board.

Validation errors and many others are now correct JSON API errors:

{
  errors: [
    { ... },
    { ... },
  ]
}

Will need to loop into this further and potentially ask for some assistance

Relationship output with fetched data

I thought I should probably spec out an issue on how this should work so we can write the relevant tests and implement.

When data is returned and there is no include filter used in the url to include related data, the relationships block should only return links informing the consumer where to fetch the actual data.

Example:

"relationships":{  
  "category":{  
    "links": {
      "related": "http://localhost:3000/api/services/1/category"
    }
  }
}

When an include filter is used in the url, relationship links should be present but also the related data itself should be returned.

Example:

"relationships":{  
  "category":{  
    "links": {
      "self": "http://localhost:3000/api/services/1/relationships/category", //<-- this also needs to be added when include filter is used
      "related": "http://localhost:3000/api/services/1/category"
    },
    "data": { "type": "category", "id": 2 }
  }
}

And an included top level key should also be included with the full data (side loading)

Example:

"included":[  
  {"type": "category", "id": 2, "attributes": { "value": "green" }}
]

TODO

  • Write tests to cover the above use cases
  • Complete "without include" filter implementation as detailed above
  • Complete "with include" filter implementation as detailed above
  • Rewrite loopback include syntax to meet JSON API expectations

Conditionally apply JSON API based on `Accept` header

Currently, once you install the component, the loopback API only supports JSON API. Data sent to the server as POST or PATCH payloads must by JSON API resources and all data output from the API is in JSON API format. Accept headers are ignored. For that matter, PUT no longer works as the API has been modified to use PATCH instead.

I'm not sure if it will ever be practical/possible, but it would be ideal to be able to switch out how the server responds based on the Accept header.

PATCH support

JSON APi specifies that PATCH must be used for updates. Currently loopback uses PUT by default.

See strongloop/loopback#445 (comment)

LoopBack explorer sends exactly the same HTTP verb which is specified in remoting metadata and used by the rest adapter. You should modify remoting data of your methods to specify a different HTTP verb (method), loopback-explorer will then pick it up automatically.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.