Building a REST interface with WordPress (WordPress App Design Part 2)

Ah, REST… I could definitely use more of that these days.

This is part 2 of the series on Modern Web App Design with WordPress. In the first article I discussed how I’d like to use WordPress to build a modern web application with a REST interface to the server, and an MVC framework such as backbone.js to build dynamic data-driven UI components that can be embedded in WordPress pages.

In this article we’ll be discussing how to build the REST interface using the wp-mvc plugin, taking advantage of all that WordPress has to offer while building a flexible interface to your server.

iStock_000009293603XSmall

The example we’re going to build is a simple  app which allows you to schedule and list events, and provide details around the venues at which they’ll take place.

This article will focus on building a REST interface to the server in the WordPress environment. Your server code will be able to call any WordPress methods, will know all about the user and will be able to leverage things like user capabilities when deciding what functions and data to expose to the user. The next article in this series will focus on building WordPress pages that make use of that REST interface in a nice organized fashion.

The beginning steps follow very closely the wp-mvc tutorial described in the wp-mvc documentation.

Download and install the wp-mvc plugin.

Either do this directly from the WordPress plugins admin dashboard or put wp-mvc directly into your wp-content/plugins folder.

Activate the wp-mvc plugin from the WordPress plugins admin dashboard.

Make sure that Pretty Permalinks are enabled and working

Generate the Application Plugin

Note: the instructions here are for Mac developers. Everything I’m doing here will translate easily for PC or Linux development, but for simplicity sake when writing, I’ll be referring to exactly what to do on a Mac.

Using a Terminal window, navigate to wp-content/plugins/wp-mvc.

$ cd <WP_HOME>/wp-content/plugins/wp-mvc

Make sure that wpmvc is marked as executable.

$ chmod +x wpmvc

Create the speaking_engagements plugin:

$ ./wpmvc generate plugin EventsAndVenues

This will generate files in <WP_HOME>/wp-content/plugins/events-and-venues that constitute the basics of the plugin.

The events-and-venues plugin will show up now in the WordPress admin dashboard. You could activate it now, but let’s wait a bit, and have the activation create some necessary tables for us first.

Create the Database Tables

When the plugin activates, we want it to either create the necessary database tables, or check if it should change any of the existing tables. WordPress’s dbDelta() function will handle that for us.

Open wp-content/plugins/events-and-venues/events_and_venues_loader.php and you’ll see the following:

Note the commented out lines in the activate() method indicating where you would set $sql and use dbDelta:

// $sql = '';
// dbDelta($sql);

The following replaces those 2 lines with the necessary SQL  to create/modify the events and venues tables:

Note: the 2 spaces in between PRIMARY KEY and the actual key for each table being created is intentional. This is to enable dbDelta() to work correctly and is documented in the codex.

Make the appropriate edits to to events_and_venues_loader.php.

Now activate the events-and-venues plugin in the WordPress plugins admin dashboard and the 2 tables will be created.

Generate the Initial Plugin Code for the Events & Venues

Go back to your Terminal, navigate to the wp-mvc plugin folder, and execute the following:

$ ./wpmvc generate scaffold EventsAndVenues Venue
$ ./wpmvc generate scaffold EventsAndVenues Event

This will generate the initial code for the models, views & controllers for the Venues and Events, which will be placed in wp-content/plugins/events-and-venues/app.

Creating Routes

We’re going to use the same mapping of CRUD to REST used by backbone.js, namely:

  • create → HTTP POST   /collection
  • read → HTTP GET   /collection[/id]
  • update → HTTP PUT   /collection/id
  • delete → HTTP DELETE   /collection/id

For example, if you want get the details of the Venue with ID 100, you would issue an HTTP GET to /venues/100. Want to create a new Venue? Post the data to /venues.  Want to delete Venue 100?  Issue an HTTP DELETE to /venues/100.  And so on…

To achieve this though, when requests come in to the server, we first need to determine if they are calls to the REST interface, and if they are, we need to decide how they’ll be handled.

Create a file in plugins/events-and-venues/app/config called routes.php that reads as follows:

The first 3 routes are what we’re mostly interested in here. The last 3 routes are the default routes, which we’re not going to be using in this exercise. But, you might as well leave them in for later.

The first route is:

MvcRouter::public_connect('rest/{:controller}', 
    array('action' => 'index_json', 'layout' => 'json'));

Whatever name is passed in as the controller is first capitalized and then ‘Controller’ is appended.

Thus a request of the form http://<server>:<port>/rest/venues/  will turn into a call to the index_json() method of the VenuesController in the controllers folder. Similarly, a request to …/rest/events will turn into a call to the index_json() method of the EventsController. When the controller is finished handling the request, the request will be passed to the json layout for a response to be formatted. We’ll be creating that layout shortly.

index_json() will be used to implement Create and Read (e.g. all venues) functionality, based on whether the request is an HTTP POST or GET respectively.

Similarly:

MvcRouter::public_connect('rest/{:controller}/{:id:[\d]+}', 
    array('action' => 'show_json', 'layout' => 'json'));

will take a request of the form http://<server>:<port>/venues/1/ and will pass it to the show_json() method of the VenuesController. show_json() will be used to process requests on individual objects and the HTTP request method:

  • HTTP GET – Read the object with the specified ID
  • HTTP PUT – Update/edit the object with the specified ID
  • HTTP DELETE – Delete the object with the specified ID

Finally,

MvcRouter::public_connect('rest/{:controller}/{:action:[^\d]+}', 
    array('layout' => 'json'));

allows us to specify custom methods to be called on our controller.

An HTTP GET of  http://<server>:<port>/venues/reviews/

would be directed to the reviews() method of the VenuesController.

Setting up the Models

Look in speaking-engagements/app/models and you’ll see 2 files: venue.php and event.php. Let’s first take a look at venue.php.

Set the Table

By default, each model will use a table whose name is the pluralized version of the model name, prefixed by the current WordPress database prefix (e.g. ‘venue’ becomes $wpdb->prefix . ‘venues’). This happens to be what we used when we created the tables when the plugin was activated. However, let’s just set the table name to be sure.

Within the model, add:

var $table = '{prefix}venues';

Each Venue can have multiple Events.  Let’s define that relationship:

var $has_many = array(
    'Event' => array('foreign_key' => 'venue_id')
);

This indicates to the Venue model that the Event model has a field called venue_id whose value will be the ID of a Venue.  This will allows us to query for the Venue for a particular event.

By default, Venue extends MvcModel and fortunately, MvcModel has default implementations for all the CRUD functionality for the model.  However, let’s do something a little more interesting here.  Let’s make use of WordPress’ roles and capabilities to ensure that the user making the request belongs to a role that has the necessary capabilities to perform the request.  For instance, let’s override the create() method of MvcModel in Venue.php as follows:

public function create($data) {
    if (current_user_can('create_venues')) {
        return parent::create($data);
    } else {
        return new WP_Error('invalid_access', 
          'User has insufficient privileges to create an event');
    }
 }

Here we’re making use of WordPress’ current_user_can() method to ensure that the role to which the user belongs has a ‘create_venues’ capability.

Here is a final implementation of venue.php that provides capability checks for all of the create, update & delete functionality provided in MvcModel:

and here is the final implementation of event.php:

I don’t do any capability checks for the reads to allow anyone, even unregistered users to read events and their venues.

Notice that in this case the Event belongs_to the Venue, whereas in venue.php, the Venue has_many Event(s).

Configuring the Controllers

The purpose of the controllers are to process requests and figure out what to do with them. It turns out that for the most part, this is identical for both Venues and Events.  By default, EventsController and VenuesController extend MvcPublicController.  Instead, change that to SEPublicController in both events_controller.php and venues_controller.php, add the following line to the top of each file (before the class declaration):

require_once('se_public_controller.php');

and let’s create the file se_public_controller.php in the controllers folder as:

As described earlier, because of the routes we declared, requests are sent to either index_json() or show_json().  We then look at the HTTP request method to determine which method should be called on the model.  Note the declarations of create_object(), read_object(), read_objects(), update_object() and delete_object().  You can override these in EventsController and VenuesController if you want some custom functionality in the controller for any of the CRUD operations.

You’ll also notice the use of $this->set() all over the place.  Here is the documentation for set.  It allows the named object(s) to be available when we build up the actual response.

We’re almost there. Hang on…

Formatting Results as JSON

Ok, we have models and controllers.  All that’s left are views.  Normally, the view in an MVC architecture would be used to generate just that, the view.  In our case, we want to generate a JSON response for any call to our REST interface.

As we indicated in our routes, all requests to our REST interface are going to use a layout called ‘json’.  Let’s create that layout.  The layout is going to generate the JSON response.

In the views folder, create another folder called ‘layouts’.  In the layouts folder create a file called json.php with the following contents:

All of the named objects from the set() calls in the controller are available in the layout.

And that’s it for the server side of things.

Let’s Try it Out!

Alright, let’s test this out.  Ultimately, in the next post in this series we’re going to be building a web app that makes calls to this REST interface.  Until we have that though, we need to make the requests manually.  I use CURL to test this out.  For the simple HTTP GET requests, you can also just type the URL into your browser.

For starters, let’s confirm this whole thing is working.  In your browser, navigate to http://blattchat.com/rest/venues/.  Make sure the trailing slash is there.  This is going to issue an HTTP GET to the server, which, based on the routes we setup, will end up ultimately in read_objects() of the VenuesController, which is actually defined in its parent, SEPublicController.

By the time you read this, you should see some venues listed in JSON format.  At this point though, I don’t have any venues, so all I see is:

Screen Shot 2013-01-17 at 8.14.37 PM

You could also do the same read with the following command in the terminal:

curl http://blattchat.com/rest/venues/

The status tells me the request was a success, but it didn’t find any venues.  Let’s add one.

curl -d '{"name":"My Venue"}' http://blattchat.com/rest/venues/

Hang on there.  Let’s look at the response:

{"status":"ERROR","errors":{"invalid_access":["User has insufficient privileges to create a venue"]}}

We’re not logged in as a user with a role which has the create_venues capability. We need to setup some roles and capabilities, and assign some users to that role.  Remember, we restricted create, update & delete to users with the appropriate capabilities.

I use the members plugin to manage roles and capabilities easily from the admin dashboard.  I’ve setup a new role  called an event_coordinator.  Here are the capabilities for that role:

Screen Shot 2013-01-17 at 6.15.33 PM

 

event_coordinators have no administrative privileges on this system, but they can do any CRUD operations on events and/or venues.  I could just assign this role to a user on the system.  For now though, I’ve simply added the necessary capabilities to my own role.  You will still be able to read events and venues since we put no read restrictions on the REST interface, but you won’t be able to create, edit or delete venues or events.  You’ll have to do that on your own WordPress installation!

Login

First let’s login to the server:

$ curl -c cookies -F log=username -F pwd=password http://blattchat.com/wp-login.php

Don’t forget to replace blattchat.com with your own server when you start building your own plugin!

This will login to the server and create a local file named cookies that contains the login info for you to submit subsequent requests as if you are logged in to the server.

CREATE

Now let’s post a new Venue.  Create a file called wang_theatre.json with the contents:

{
    "name": "Wang Theatre",
    "address1": "270 Tremont St.",
    "city": "Boston",
    "state": "NY",
    "zip": "02116"
}

And post it using your login credentials:

$ curl -b cookies -d @wang_theatre.json http://blattchat.com/rest/venues/

‘-b cookies’ tells curl to use the cookies file which was created when you logged in.

‘-d @wang_theatre.json’ tells curl to post the contents of the wang_theatre.json file in the body of the request.

Don’t forget, the trailing slash does need to be there or the request will fail.  This is based on how wp-mvc deals with the request URL.  You should see a result similar to this:

{"status":"OK","object":{"id":"1","name":"Wang Theatre","url":null,"description":null,"address1":"270 Tremont St.","address2":null,"city":"Boston","state":"NY","zip":"02116"}}

This a JSON representation of the new Venue as stored on the server.   The response includes a status, any errors (none here) and the object that was just created.  Note the id of the new object.  This is how we will refer to the Venue in subsequent requests.

UPDATE

As you probably noticed, we got the state wrong there.  The state should be ‘MA’.  Let’s change that:

curl -b cookies -X PUT -d '{"state":"MA"}' http://blattchat.com/rest/venues/1/

‘-X PUT’ tells curl to not use the default HTTP POST but instead do an HTTP PUT.

DELETE

And now we can delete our venue as follows:

curl -b cookies -X DELETE http://blattchat.com/rest/venues/1/

And there you go!  We’ve tested all the CRUD operations on Venues.

You could do the same with the Events.  If you want to create an event for a specific venue, you would just specify the appropriate venue_id.

curl -b cookies -d '{"name":"Create the Web","venue_id":"1"}' http://blattchat.com/rest/events/

Then, you can do some interesting things such as getting the list of events, and include their Venue.  Since we defined that relationship in the Event model, we can do the following:

curl http://blattchat.com/rest/events/?includes=Venue

which will include the Venue model with each Event listing.

Or, if we just want to get all the Events for the Venue with ID 1, we can url_encode the phrase ‘conditions[venue_id]=1′ and use that as the URL query:

curl http://blattchat.com/rest/events/?conditions%5Bvenue_id%5D=1

The documentation on wp-mvc gives you some ideas of what the different possibilities include.

The next article in this series is all about the front end, how to build a data-driven application embedded in a WordPress page that makes use of the REST interface we built here.

That’s it for now.  I encourage you to try this on your own, play around with it, poke holes in it and let me know your thoughts or questions.

 

Tagged , , , . Bookmark the permalink.

15 Responses to Building a REST interface with WordPress (WordPress App Design Part 2)

  1. Greg Turner says:

    Thank you very much for this. Am learning a lot and looking forward to the next “installment”.

  2. Pingback: Building Data-Driven Web Applications with WordPress (WordPress App Design part 3) - Alan Greenblatt

  3. Pingback: Modern Web App Design with Wordpress – Part 1 - Alan Greenblatt

  4. Jake says:

    Love it. It seems like some code is missing though!

  5. a77 says:

    Great stuff – I had arrived at this same solution through independent investigation – i.e. wpmvc + REST + AJAX – but was missing some know-how – as I’m new to PHP – which this article answers very nicely. I thinks this is an excellent little recipe for writing nice clean wordpress extensions.

  6. jason says:

    great! simple handy and clear!
    Thank you very much. 大赞!

  7. Omer says:

    Alan — great tutorial. But I’ve hit a problem along the way. After installing the members plugin and creating the event_coordinator role, I can’t see any reference to events or venues in the list of role capabilities. I can however see the other stuff like edit_post, delete_post etc. Any advice?

    • agreenblatt says:

      Thanks Omer. That’s because edit_post, delete_post, etc. Are capabilities put there by WordPress by default. If you’re editing your new event_coordinator role, at the bottom of the page it should say “Custom Capabilities” with a button titled “Add New Capability”. Click the button, type the name of the new capability you want to add, then click the “Update Role” button. Now your event_coordinator role will have that new capability. As well, that new capability will show up now, off by default, for all other roles.

  8. Hayder says:

    Thanks for the tutorial. It’s really helpful to a beginner like me. I’m modifying it slightly to be one table, but I’ve run into a snag. When I test it with http://domain.com/rest/venues I get

    “Fatal error: Call to a member function paginate() on a non-object in C:\xampp\htdocs\wp-content\plugins\farm-d-b\app\controllers\se_public_controller.php on line 107″

    I’m wondering if it’s my venues_controller file because mine is empty other than the ‘requires_once’ statement and the class declaration, and I can’t find in the tutorial what it should be. Can you point me in the right direction?

    • Hayder says:

      Sorry, the names may seem confusing. I named my plugin farmdb instead of events and venues. My controller is called farms_controller.php

  9. Warwick Jones says:

    Nice article, however do you have an update on the wordpress plugin wp-mvc plugin, because This plugin hasn’t been updated in over 2 years. It may no longer be maintained or supported and may have compatibility issues when used with more recent versions of WordPress.

  10. Prabhu bakiyaraj says:

    Nice one. But i need to develop an application with wpmvc in wordpress along with blade. could you please help me with an application that illustrates basic CRUD operation?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>