Published on

How to use a CakePHP API as the data backend for Ember in 30 minutes

In this follow-up post to How to make your CakePHP 3 API produce JSON API we will show you how easy it is to use your CakePHP API as the backend for an Ember application, allowing you to keep benefiting from the extremely powerful CakePHP ORM whilst also enjoying all the frontend-goodies provided by Ember.

The tutorial:

  • Provides step-by-step instructions
  • Ends with a full CRUD implementation
  • Adds background information to help you understand the underlying concepts

CakePHP Users

Even though this tutorial does not touch a single piece of PHP code the learning curve should be minimal since both frameworks share a lot of characteristics:

  • Convention over configuration
  • A stack of proven tools that "just work"
  • Command line tools for baking/generating code
  • Rapid Development; getting productive, very fast
  • Highly maintainable, painless upgrades
  • Suitable for both small and ambitous applications

Before We Begin

This is part six of the CakePHP 3 REST API tutorial series:

  1. How to build a CakePHP 3 REST API in minutes
  2. How to use a CakePHP 3 REST API
  3. How to prefix route a CakePHP 3 REST API
  4. How to add JWT Authentication to a CakePHP 3 REST API
  5. How to make your CakePHP 3 API produce JSON API
  6. How to create an Ember application with (CakePHP) JSON API backend in 30 minutes

Before starting this tutorial make absolutely sure:

  • That you have the cake3api.app API up-and-running
  • That it is producing valid JSON API
  • That it is allowing CORS requests

You may create the API by either:

Assumptions

This tutorial assumes:

  • The hostname serving your Ember application is cakebox (replace with your own system name where applicable)
  • The FQDN of your API is cake3api.app (replace with your own FQDN where applicable)
  • Chrome with the Ember Inspector add-on as your client browser

1. TLDR just give me the Ember end-state source files

Novice users should skip this step.

git clone https://github.com/bravo-kernel/application-examples
cd application-examples
cd blog-how-to-use-a-cakephp-api-as-the-data-backend-for-ember-in-30-minutes
npm install
ember serve

Browse to http://cakebox:4200

2. Requirements

This tutorial requires the installation of nodejs, ember-cli, phantomjs, and bower on your server.

Since every system will have different requirements you should consider the following instructions (taken from an installation on cakebox using Ubuntu 14.04) as nothing more than an example:

curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get update
sudo apt-get install nodejs
npm install -g ember-cli
npm install -g phantomjs
npm install -g bower

3. Creating the Ember application

Ember comes with the ember CLI command which is similar to CakePHP's cake and used to generate applications, routes, models, etc. We will generate a fresh Ember application named ember-frontend by running:

cd /path/to/your/projects/directory
ember new ember-frontend
cd ember-frontend
Screenshot of ember-cli generating the Ember application

4. Meeting Ember

Since this is basically all that is needed to create a new Ember application this might be a good moment to take a quick look at the files and folders to get a quick first impression of the application structure.

To see your Ember application in action run ember serve and browse to http://cakebox:4200:

Screenshot of Ember welcome page

To continue this tutorial without the Tomster mascotte open templates/application.hbs and remove the following line:

{{welcome-page}}

5. Creating the Ember Data Adapter

Ember Data's (now default) JSONAPIAdapter is the awesome/amazing glue responsible for automatically constructing all JSON API calls required to communicate with your API backend. Generate it by running:

ember generate adapter application

Now open app/adapters/application.js and add the connection settings pointing to your API so it looks like this:

import DS from 'ember-data';

export default DS.JSONAPIAdapter.extend({
  host: "http://cake3api.app",
  namespace: "api"
});

Please note that we MUST set the namespace here because we created a CakePHP prefixed route in tutorial #3

6. Creating the Ember models

Ember Data must somehow be made aware of the models/resources available in our CakePHP API (and their relationships) and amazingly enough all that is required for that to happen is generating an "Ember model".

Since our API is serving cocktails we create a corresponding Ember model by running:

ember generate model cocktail

Now open app/models/coctkail.js and specify which attributes you want to be available in the frontend:

import DS from 'ember-data';
import attr from 'ember-data/attr';

export default DS.Model.extend({
  name: attr(),
  description: attr(),
  created: attr(),
});

Do not forget to add ember-data/attr

7. Adding the index route

To start with Ember uses "routes" which are comparable with CakePHP controllers and Handlebars "templates" which are comparable with CakePHP templates. To generate the route we will use to hook into our API's index action we run:

ember generate route cocktails/index

This will:

  • Create app/routes/cocktails/index.js
  • Create app/templates/cocktails/index.hbs
  • Update app/router.js to include the newly generated route

Now open app/routes/cocktails/index.js and add our model so all cocktail data is automatically fetched from our API when accessing this route/page.

import Ember from 'ember';

export default Ember.Route.extend({
  model() {
    return this.store.findAll('cocktail');
  },
});

All that is needed now is updating the template so it will show the fetched data so let's update app/templates/cocktails/index.js and make it look like this:

<h2>Ember Data JSONAPIAdapter - Index route</h2>

<table>
  {{#each model as |cocktail|}}
      <tr>
          <td>{{cocktail.id}}</td>
          <td>{{cocktail.name}}</td>
          <td>{{cocktail.description}}</td>
          <td>{{cocktail.created}}</td>
      </tr>
  {{/each}}
</table>

Done, seriously.

Verify

Now that you have created your first API-driven page verify Ember is actually fetching from your API:

  • Open Chrome
  • Browse to your Ember application at cakebox:4200/cocktails
  • Make sure it displays the records as found in your CakePHP database:
Ember showing data fetched from CakePHP API

8. Using Ember Inspector

The Ember Inspector in Chrome will help you get a better understanding of the underlying API magic so let's zoom in on that subject before continuing.

Inspecting API Resources

The API resources used by your Ember frontend can be found by:

  • Opening chrome
  • Pressing F12
  • Selecting the Network tab
  • Browsing to cakebox:4200/cocktails
Ember Inspector API network resource
  • upper arrow: tells us that a network resource named cocktails was fetched using jquery (executed by Ember Data)
  • lower arrow: tells us Ember Inspector is recognizing the page as valid Ember

Inspecting API Resource Headers

To zoom in on the API resource and inspect the headers simply click cocktails which should give you something like:

Ember Inspector zoom in on API network resource headers
  • Upper arrow: proves the resource is fetched from our CakePHP API
  • Center arrow: tells us the CakePHP API is responding with JSON API
  • Lower arrow: tells us Ember Data is automatically sending the JSON API Accept Header with all requests

Inspecting the API response

You will be able to see the JSON API response produced by the API by clicking the Response tab. This should look more than familiar since you have implemented JSON API during the previous tutorial (pretty cool).

FYI the Preview tab will show the exact same results but objectied for easy click-through.

Ember Inspector show API response

Inspecting Ember Data records

The Ember Inspector is really useful when we want to see how our API data is being used by Ember. To make this more clear:

  • Select the Ember tab
  • Select the Data container
  • Select the cocktail model type

You should notice two things:

  • The attributes are corresponding to the Ember cocktail model we created earlier
  • And are thus missing the modified attribute we excluded (even though our API is servicing it)
Ember Inspector Ember Data main

Inspecting Ember Data record details

Every record contains very detailed information which is revealed when clicking a record.

Relationship information (belongsTo, hasMany) will appear in the Flags section when applicable.

Ember Inspector Ember Data record details

9. Adding the delete action

Ember uses template actions to let users interact with data so let's add one that will automatically construct and send the correct DELETE request to our API.

Moment, since all template actions require a corresponding (named) event in the routes file first open app/routes/cocktails/index.js and update it to look like this:

import Ember from 'ember';

export default Ember.Route.extend({
  model() {
    return this.store.findAll('cocktail');
  },

  actions: {
    deleteCocktail(cocktail) {
      let confirmation = confirm('Are you sure?');

      if (confirmation) {
        cocktail.destroyRecord();
      }
    }
  }
});

Now that the deleteCocktail event/action is in place all that is left to do is updating the index template with a button so users can trigger the action. To do so, open templates/cocktails/index.hbs and update it to look like this:

<h3>Ember showing data fetched from CakePHP API's index action</h3>
<table>
  {{#each model as |cocktail|}}
      <tr>
          <td>{{cocktail.id}}</td>
          <td>{{cocktail.name}}</td>
          <td>{{cocktail.description}}</td>
          <td>{{cocktail.created}}</td>
          <td><button class="button" {{action 'deleteCocktail' cocktail}}>Delete</button></td>
      </tr>
  {{/each}}
</table>

Verify

Verify you are able to delete records in your API by using the Ember page by:

  • Opening Chrome
  • Pressing F12
  • Selecting the Network tab
  • Browsing to your Ember application at cakebox:4200/cocktails
  • Making sure it now displays Delete buttons
  • Pressing one of the Delete buttons (and confirming the delete)
Ember delete action page

If things went well:

  • Ember automagically made two requests to your API (see the Chrome Network tab):
    • One for the CORS OPTIONS
    • One for the actual DELETE
  • Clicking those requests should now produce information that makes sense to you
  • The record was deleted from your CakePHP database
  • Refreshing the page should no longer show the deleted record
  • You should by now be quite impressed with how easy that actually was
Ember delete action two requests

10. Adding the add action

Without going into details we will create an Ember nested route to create a dedicated page for our add action by running:

ember generate route cocktails/add

This will:

  • Create app/routes/cocktails/add.js
  • Create app/templates/cocktails/add.hbs
  • Add the nested route to router/router.js

Similar to the delete action we will first create a named event so update app/routes/cocktails/add.js to look like this:

import Ember from 'ember';

export default Ember.Route.extend({
  model() {
    return this.store.createRecord('cocktail');
  },

  actions: {
    addCocktail(cocktail) {
      let confirmation = confirm('Are you sure?');

      if (confirmation) {
        cocktail.save();
      }
    }
  }

});

Open app/templates/cocktails/add.hbs and add this basic form the user will use to trigger the addCocktail action:

<h3>New Cocktail:</h3>
<form>
  <label>Name: {{input value=model.name}} </label>
  <label>Description: {{input value=model.description}} </label>
  <button class="button" {{action 'addCocktail' model}}>Add</button>
</form>

Verify

Verify you can add a new record to your CakePHP database using your Ember frontend by:

  • Browsing to cakebox:4200/cocktails/add
  • Making sure the input fields are there
  • Filling the form fields name and description
  • Pressing the Add button

If things went well:

  • Ember automagically made two requests to your API (see the Chrome Network tab):
    • One for the CORS OPTIONS
    • One for the actual POST request
  • You should be able to see the new record in your CakePHP database
Screenshot of Ember add action page

11. Adding the view/edit actions

Almost there.

ember generate route cocktails/edit

Update app/routes/cocktails/edit.js so it looks like this:

import Ember from 'ember';

export default Ember.Route.extend({
  model(params) {
    return this.get('store').findRecord('cocktail', params.id);
  },

  actions: {
    saveCocktail(country) {
      let confirmation = confirm('Are you sure?');

      if (confirmation) {
        country.save();
      }
    }
  }
});

To handle the id parameter open app/router.js and replace this.route('edit'); with:

this.route('edit', { path: "/edit/:id" });

Update app/templates/cocktails/edit.hbs so it looks like this:

<h3>Edit existing Cocktail (id = {{model.id}})</h3>
<form>
    <div>{{input value=model.name}}</div>
    <div>{{input value=model.description}}</div>
</form>

<button class="button alert small" {{action 'saveCocktail' model}}>Save</button>

All done, browse to http://cakebox:4200/cocktails/edit/3 and verify that you can view, edit and save the record.

Screenshot of Ember view/edit action page

Bonus: Relational Data

For simplicity's sake we did not touch the subject in this tutorial but... Ember Data will also (automatically) detect and handle your API's belongsTo and hasMany relationships as can seen in the following screenshot:

Screenshot of Ember data relationships

Please consult the Ember documentation for more information but enabling the above relationships was as easy as adding them to the Ember model like this:

export default DS.Model.extend({
  name: attr(),
  code: attr(),
  native: attr(),
  currency: belongsTo('currency'),
  cultures: hasMany('culture')
});

Bonus: Validation Errors

Because your CakePHP API is producing JSON API compatible (validation) errors... those are handled by Ember Data automatically as well, making them instantly availble in your frontend as can be seen in the screenshot below where:

  • Your API is returning the 422 validation error along with validation error messages
  • Ember Data picks up the validation messages and makes them avaible for display on the page
Screenshot of Ember data validation errors

Getting help

Both CakePHP and Ember have very friendly online communities that are more than willing to help you out if you encounter any problems so make sure to join:

Additional reading

Eternal hat-tip to Phally for inspiring (and actually prototyping) this many years ago.