Ember Data, coupled with JSON API does a great job of returning validation errors from the server to display to the user. Sometimes however, you need to validate the field data before submitting the entire record. There are some good plugins to handle client side validation. But what if you need to validate the field data at the server level without attempting to create or update a record. For example, if you need to check if an input value is unique. The JSON API spec does not describe the specifics of this situation. By adding a validations endpoint, and an Ember service to delegate requests, we can accomplish this the Ember way without too much trouble.

To demonstrate, I have created a new app with a simple User form. The /users endpoint (using ember-cli-mirage) is configured to reject the record if the username submitted is not unique. We can test this by attempting to submit the form and getting the following error:

Username must be unique. (from ember-data)

Try it: http://ember-user-form.surge.sh/

To take this to the next step and validate the form fields in-line, we need to create a new validations endpoint at the server.

mirage/config.js

import { Response } from 'ember-cli-mirage';

export default function() {
  //...
  this.get('/validations/:property', (schema, request) => {
    let property = request.params.property;
    let params = request.queryParams;
    switch (property) {
      case 'user-username':
        return validateUserUsername(property, params, schema);
      default:
        return validateDefault(property, params);
    }
  });

  function validateUserUsername(property, params, schema) {
    let username = params['filter[value]'];

    if (usernameExists(username, schema)) {
      return new Response(422, {}, {
        "errors": [
          {
            "status": "422",
            "source": { "pointer": "/data/attributes/username" },
            "title": "Invalid Attribute",
            "detail": "Username must be unique. (from validation service)"
          }
        ]
      });
    } else {
      return validateDefault(property, params);
    }
  }

  function validateDefault(property, params) {
    let field = property.split('-').pop();
    let attributes = {
      [field]: params['filter[value]'],
    };
    return validationSuccess(property, attributes);
  }

  function validationSuccess(property, attributes) {
    return {
      "data": {
        "id": property,
        "type": "validations",
        "attributes": attributes,
      },
    };
  }

  function usernameExists(username, schema) {
    let hasMatchingUsers = schema.users.where(function(user) {
      return user.data.attributes.username === username;
    }).models.length;
    return (hasMatchingUsers || username === 'not unique');
  }
}

 

Next create a new validation service in your app.

app/services/validation.js

import Ember from 'ember';
import $ from 'jquery';

export default Ember.Service.extend({
  store: Ember.inject.service(),
  baseURL: '/',

  check(model, fieldName, value) {
    let { modelName } = model.constructor;
    let params = {
      model: modelName,
      field: fieldName,
      value,
    };

    return this._apiCall(params).then((response) => {
      return { state: 'success', response: this._successResponse(response) };
    }, (response) => {
      return { state: 'error', response: this._errorResponse(response) };
    });
  },

  _apiCall(params) {
    let url = this._apiUrl(params);
    return Ember.RSVP.resolve($.getJSON(url));
  },
  _apiUrl(params) {
    return this.baseURL + 'validations/' +
      params.model + '-' + params.field + '?filter[value]=' + params.value;
  },

  _successResponse(response) {
    return response;
  },

  _errorResponse(response) {
    let errors = response.responseJSON.errors;
    errors.forEach(function(item, index, errors) {
      errors[index] = {
        attribute: '',
        message: item.detail,
      };
    });

    return errors;
  },
});

Finally, make (or modify) your component to handle the input logic, and communicate with the new validation service. In this example, I’m using Alex Matchneer’s awesome `ember-concurrency` addon to ease the pain of canceling and restarting validation requests. There’s a bit to this step, so instead of showing the code here, see the diff on GitHub.

Now if you enter a username of not unique and tab to the next input, or wait 500 milliseconds, you’ll be presented with the validation error from the validation service.

Username must be unique. (from validation service)

Try it: http://ember-field-validation.surge.sh/

Hopefully this can help with your Ember.js field validation techniques. The full code for this demo is available on GitHub at: https://github.com/ricog/ember-server-side-field-validation-demo