Decoupling Drupal and Consuming with Symfony2, JavaScript or Unicorns

Drupal Developer Days Dublin
June 2013
@juampy72 & @justafish

About the presenters

juampy justafish


  • Meet @daniel_jacobson, ex App Development Director at
  • Built a strong API which could be consumed by a long list of clients
  • The National Public Radio's CMS has been working since more than 10 years ago!

Amazon Infrastructure

  • All teams expose their data and functionality through APIs
  • No other form of communication is allowed
  • Use whatever technology you like
  • "Anyone who doesn't do this will be fired. Thank you; have a nice day!" - Jeff Bezos

Secret Projects We've Worked On

  • Big Drupal multi-site install that contained a lot of bad HTML
  • Utilising feeds to drive Menus and other front end items
  • Disqus style commenting

create once, publish everywhere


  • Easier to upgrade front and backends separately.
  • Makes frontend work fun again! We can also use wider resources
  • Backend focuses on just having a good content model
  • Performance


  • Not applicable to all sort of projects
  • No Views module (not necessarily a drawback :) )
  • A considerable amount of time is needed for the first project


Drupal 7 + Services* + OAuth


For Drupal 8, just enable the REST module and tada!

* or CreateAPI or restws or Views Datasource or a custom output

OAuth setup

Server auth

Endpoint auth

Client creation

Client access keys

Symfony2 client


Homepage controller

class DefaultController extends Controller
  public function indexAction()
    $client = new Client($this->container-> getParameter('backend_host'));
    $request = $client->get('/node.json');
    $response = $request->send()->json();

    $recipes = array();
    foreach ($response['list'] as $recipe) {
      $recipes[] = $recipe;
    return $this->render('LullabotRecipeBundle:Default:index.html.twig',
      array('recipes' => $recipes));

Homepage template

{% extends '::base.html.twig' %}

{% block body %}
{% endblock %}

Submit recipe

Submit recipe controller

  $form = $this->createFormBuilder()
    ->add('title', 'text')
    ->add('body', 'textarea')
    ->add('prep_time', 'integer')

  if ($request->isMethod('POST')) {
    if ($form->isValid()) {
      $data = $form->getData();
      $client = new Client($this->container->getParameter('backend_host'));
      $client->addSubscriber(new OauthPlugin(array(
        'consumer_key'  => $this->container->getParameter('oauth_consumer_key'),
        'consumer_secret' => $this->container->getParameter('oauth_consumer_secret'),
      $request = $client->post('/api/node', null, array(
        'title' => $data['title'],
        'body' => array('und' => array(array('value' => $data['body']))),
        'field_recipe_prep_time' => array('und' => array(array('value' => $data['prep_time']))),
        'type' => 'recipe'

jQuery Mobile Client
Can utilise phone features using

Same Origin Policy

By default, you cannot use JSON feeds from different domains e.g. cannot use a feed from
  • CORS -

Simple Tips for Building APIs

  • Use version numbers in your URLs e.g. /api/1.0/recipes.json
  • Don't expose raw Drupal field names as keys
  • Don't settle for a bad data structure because it's the default views datasource/services output
  • Stay consistent with key naming
  • Try to just expose your data and not process it. This is the job of your clients
  • Read this book! APIs: A Strategy Guide

Thanks! Questions?