Waiter, There’s a WordPress in My Web App!

If you’ve ever been a part of developing custom software, you’ve probably seen some features turn out to be much more complicated than anticipated. Sometimes, it’s due to unforeseen technical constraints. Other times, it’s a case of not fully understanding the nature of the feature—a situation that led me to an unexpected use for WordPress.

We were developing a new version of a web app with one major activity, plus a few content pages off to the side. We had de-prioritized these content pages, estimating that they would be very quick to implement with some basic HTML to hold the content and a little CSS to make it look decent. The problem was, we didn’t understand the dynamism involved in the content pages.

While these pages appear static from the user’s perspective, the content actually depends on  what type of user you are—including which user group you’re affiliated with and which language you speak. One of the user groups has a sub-group for Spanish speakers, so all pages for that subgroup need to show the content in Spanish, not English. The cherry on top of the complexity sundae was that the customer wanted to be able to edit this content, and really wanted a WYSIWYG editor as opposed to editing the HTML.

By the time we properly understood these requirements, we had already spent most of the project time and budget on the major activity in the app, so we had little ability to devote more resources to the feature. Moreover, even with triple our initial budget, building a proper CMS was out of the question. So what could we do? Luckily, the designer on our project had heard of a little thing called WP-API.

Getting to Know WP-API

WordPress is far and away the most popular open-source Content Management System. It supports a mind-boggling number of plugins, including one called WP-API which allows access to posts via a JSON interface. Because WP-API lets you specify filters when you ask for posts, we realized we could use this feature to leverage the power of WordPress, a neat solution which would provide the dynamic content we needed and the content management experience the customer wanted.

Our approach has been referred to as “headless CMS.” Essentially, WordPress lives as a separate service on our server, accessible only to our app’s backend (using Rails) and to computers behind the customer’s firewall. When the client (using Ember) requests content from the backend, the backend then requests content from the WordPress site based on the current user and the type of content requested. This image provides a fairly good approximation of what’s happening:

WordPress API

When creating posts in WordPress Admin, the customer specifies categories which indicate the type of content and the intended user. Consequently, our search can identify the right post given the type of content and the current user.

Handling Content Requests

For technical reasons, I was not able to add all of the filters I wanted into the query, so I had do some filtering on our backend based on the list of matching posts. After that point, I had HTML ready to send to the client for rendering—almost.

If the content contained images, documents, or links to other posts, I still had to do something to make those resources available to the site. I couldn’t use the links generated by the WordPress WYSIWYG editor. Firstly, the URL isn’t accessible to the outside world, and secondly, even if it were, the content is supposed to be gated according to user group. So I had to rewrite the URLs to point back through our backend, and then whitelist those URLs for later retrieval by the current user. Additionally, if a post linked to another post, I had to rewrite that link to point to a general “content holder” route in Ember. Here’s a high-level view of what’s going on:

def load_content(ctx)
  json = WordPressService.retrieve_posts ctx
  if json.present?
    posts = WordPressService.parse json
    post = WordPressService.find_post ctx.current_user_group, posts
    if post.present?
      rewritten_post = WordPressService.translate_content_and_links post.raw_content
      WordPressService.whitelist_urls ctx.session_access, rewritten_post.urls_to_whitelist
      return rewritten_post.to_h

With that code in place, I could use the returned HTML in the Ember app, knowing that users could see it, images and all. Links to files, and even links to other blog posts, worked. The content loaded fairly quickly, and we could easily add caching later if need be.

Streamlining Client-Side Links

So far, the content worked, but the experience of going from one WordPress post inside our app to another wasn’t very smooth. Because Ember couldn’t detect that <a href='/content/post-name'> should transition via an Ember route change, it reloaded the whole site whenever we clicked a link. To fix this, I added a click handler in the “content holder” component to hijack the link when necessary:

var matchContent = window.location.origin + '/content/';
if (evt.target.href && evt.target.href.startsWith(matchContent)) {
  var path = evt.target.href.substring(matchContent.length);
  path = path.match(/[\w-]*/)[0];
  this.transitionTo('content', path);

Putting It All Together

WordPress in a web app? Absolutely. WP-API enabled us to deliver the feature to our customer, on time and wrapped in a CMS experience that surpassed their previous system. To me, it really shows the power of combining tools through the polyglot approach. If you keep your eyes (and your mind) open for creative solutions, you may just find a simple way to solve a complex problem.