Dealing with errors in Ember with Custom API Responses

ember Sep 11, 2019

Ember version used: 3.8 (LTS)

Backend: Rails

I started working with Ember recently and one of the things I've had to deal with is dealing with custom API responses. Ember usually expects incoming data to follow the JSON:API format like the one below:

{
  "data": [{
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "JSON:API paints my bikeshed!",
      "body": "The shortest article. Ever.",
      "created": "2015-05-22T14:56:29.000Z",
      "updated": "2015-05-22T14:56:28.000Z"
    },
    "relationships": {
      "author": {
        "data": {"id": "42", "type": "people"}
      }
    }
  }],
  "included": [
    {
      "type": "people",
      "id": "42",
      "attributes": {
        "name": "John",
        "age": 80,
        "gender": "male"
      }
    }
  ]
}

Note - for a lot of people who've not worked with JSON:API, the above JSON example would look strange. data holds the document's primary data. relationships key holds the id and type of the person that has an article (from the example). It allows a client link all included resource objects without having to make extra GET requests. included holds the attributes of the person. included is used to hold an array of included resources. You can get more info on JSON:API here

Ember is really nice and understands that not everyone would prepare their data in this format and provides us with methods that we can use to convert our API responses to the format that Ember loves.

I recently had to deal with displaying errors in my Ember app when a record fails to get saved and it was pretty tricky as I couldn't find any clear way to do it using Ember's RestSerializer. I'm going to be walking us through my implementation.

import DS from 'ember-data';
import ApplicationSerializer from './application';

const { errorsHashToArray } = DS;


export default ApplicationSerializer.extend(EmbeddedRecordsMixin, {
  extractErrors(store, typeClass, payload) {
    payload.errors = errorsHashToArray(payload.errors[0]);

    return this._super(...arguments)
  }
});

Ember uses extractErrors to extract model errors after a save() fails. It normally expects your errors to be in the .errors property of the payload object, so the JSON object you send in should have an errors root property.

extractErrors normally expects an array of objects in JSON:API format. We first make use of errorsHashToArray to convert our incoming errors hash to an array of errors.

We make use of it to convert a response payload in this format:

{
    "errors": [
        {
            "name": [
                "can't be blank"
            ],
            "description": [
                "can't be blank"
            ]
        }
    ]
}

To this:

{
  [
      {
        title: "Invalid attribue",
        detail: "can't be blank",
        source: { pointer: "/data/attributes/name" }
      },
      {
        title: "Invalid attribue",
        detail: "can't be blank",
        source: { pointer: "/data/attributes/description" }
      }
   ]
}

After which we can then call this._super , passing in the new array, which is then mapped to the relevant fields on the model.

Accessing the errors

You can access the returned errors from your route files. Our errors would be available on the model after the Promise is rejected and we can retrieve them when we want to.

newRecord.save()
        .then(() => console.log("Yay! Works"))
        .catch(() => {
          this.currentModel.set('nameErrors', newRecord.get('errors.name')) // errors on the name attribute
          this.currentModel.set('guestsErrors', newRecord.get('errors.guests')) // errors on the guests attribute
          this.currentModel.set('descriptionErrors', newRecord.get('errors.description')) // errors on the description attribute
          this.currentModel.set('startTimeErrors', newRecord.get('errors.startTime')) // errors on the startTime attribute
          this.currentModel.set('endTimeErrors', newRecord.get('errors.endTime')) // errors on the endTime attribute
        })

What we are doing above is using a get to retrieve errors on a particular attribute and setting it  as a property in the currentModel object. (NB: I couldn't access model so I went with the next best thing :smiles:).

Displaying them

The errors can then be accessed in your templates for your users to see just how good you are with this kind of things. (Revel in your glory as a 10x dev, more like 1/2x or 1/99x).

{{#each model.nameErrors as |error| }}
  <div class="help is-danger">
    <ul>
      <li>{{capitalize-word error.attribute}} {{ error.message }}</li>
    </ul>
  </div>
{{/each}}

You can have more than one error on an attribute so you'd ideally want to loop over each and display them to the user. You could also concatenate all and display as one long message. Ember allows you to do so.

Resources

Hope this tutorial was helpful. Expect more to come soon. Bye

Onyekachi Okereke

Kachi is currently a software developer with Andela. He enjoys watching shows on Netflix and writes once in a while. Also a really shy dude