How to set a Backdrop cms as backend and a Laravel as frontend

Robert Garrigos

Robert Garrigos

Jul 26, 2019 — 11 mins read

I fall in love with Backdrop, a Drupal 7 fork, a few years after Drupal 8 let me down as a single persone web shop. Backdrop is a great tool to build up simple, and not so simple, web sites. However, when I was in the need of an even simpler web site, like a single page site, Backdrop seemed too much for it (due to an excessive template system, at my opinion). In my search for a solution I came up with Laravel and quickly fall in love again.

Once you get use to it, it is very easy to create a single page site with Laravel and add any logic/pages as soon as they are needed. However, backends with Laravel are tedious. Of course there are solutions out there but they are either free and not as good as Backdrop (really great at creating types of content with any types of fields, views, user permissions, etc) or they are not free... and not as good as Backdrop. Thus, I wanted a solution, a way of taking all the advantage of the Backdrop's backend and being able, at the same time, to build a simple site with backend content created while in full control of the frontend. Then I found the Headless Backdrop module and saw the light.

The headless module creates several end points at a Backdrop site from where you can pull data in JSON format. Thus, I just needed a way to pull in the data to a Laravel project. GuzzleHttp seemed the right composer package to do so and, in fact, I test it, and used it, with a project I had at that time. It worked very well. However there was some code I had to write which could be refactored as a composer package, and that's what I did: I created the Backdrop headless client composer package.

I presented you all the ingredients to get this recipe done, so let's cooke it.

Backdrop backend

The easiest way to install a local Backdrop to test this recipe (or any other "recipe", really) is with Lando. Lando works on top of docker and makes creating local environments just a breeze. Follow these instructions to install a Backdrop site with lando, which basically means to install lando (which already comes with docker), download Backdrop, run lando init at command line inside the Backdrop directory, answer a few questions lando will pose you and, finally, run lando start. You will be presented with a local url for your newly created Backdrop instance. Click that url and follow the instructions to finish installing Backdrop. Lando will set up the database for you (isn't that nice?).

Once Backdrop is installed, you need to create some content and install and configure the headless module (as of today, august 4th 2019, this module has some pull requests waiting to be applied which are needed to use it as I describe here, but you can pull my own fork). After that, your Backdrop will have some end points to pull the data from. We'll talk about them later on. Let's go for the front end, now.

Laravel frontend

There are several ways to develop a laravel project locally but I do so, again, with lando. Here is a good documentation on how to install laravel with lando (you can skip database and migration as we are not going to need them, but it's good to keep it in mind for later). Once this is done, let's add the ring that rules them all.

Backdrop headless client

Let's install this laravel package, which will keep in contact the backend with the frontend, by running:

composer require robertgarrigos/backdrop-headless-client

Then we need to set the Backdrop api server url within the .env file. In our case it would be something like this:

BACKDROP_API_SERVER="http://backdrop.lndo.site"

And that really is all we need. We could go to our controllers in laravel and use the methods provides by this package to get our Backdrop data. However, I want to explain in more detail what are the config options we have in this laravel package and how can they make our life a bit easier. So let's talk about node mappig.

Node mapping

If you look at the JSON exposed at a node end point, which could be something like http://backdrop.lndo.site/api/v2/node/post/8, you might get something like this:

{
"field_character": {},
"field_credits_1": {},
"field_credits_2": {},
"field_date_1": {},
"field_date_2": {},
"field_description": {},
"field_image": {},
"field_image_1": {},
"field_image_2": {},
"field_link_1": {},
"field_link_2": {},
"field_tags": {},
"field_text_1": {},
"field_text_2": {},
"field_title_1": {},
"field_title_2": {},
"field_video_1": {},
"field_video_2": {},
"#pre_render": [],
"#entity_type""node",
"#bundle""contradiction",
"links": {},
"#view_mode""full",
"#theme""node__contradiction__full",
"#node": {},
"#langcode""ca",
"#contextual_links": {}
}

This is just an example of a content type post with plenty of custom fields. They are all collapsed here, but if I show you just the first unfold field:

"field_character": {
  "0": {
    "#type""link",
    "#title""Esquerra",
    "#href""taxonomy/term/14",
    "#options": []
  },
  "#theme""field",
  "#weight""1",
  "#title""Character",
  "#access"true,
  "#label_display""inline",
  "#view_mode""full",
  "#language""und",
  "#field_name""field_character",
  "#field_type""taxonomy_term_reference",
  "#field_translatable"false,
  "#entity_type""node",
  "#bundle""contradiction",
  "#object": {},
  "#items": [
    {
      "tid""14",
      "taxonomy_term": {
        "tid""14",
        "vocabulary""characters",
        "name""Esquerra",
        "langcode""und",
        "description"null,
        "format"null,
        "weight""0",
        "parent"null,
        "path": {
          "pid""21",
          "source""taxonomy/term/14",
          "alias""characters/esquerra",
          "langcode""und",
          "auto"true
        }
      }
    }
  ],
  "#formatter""taxonomy_term_reference_link"
},

We see there are a lot of options. Let's image we need to print out, or just use it, the value of the property "alias" in our blade file of laravel. I'll get into deeper of this later, but just trust me on this for a moment. This is what we would need to write in our blade file:

$node->field_character->{'#items'}[0]->taxonomy_term->path->alias

No comments. Wouldn't it be much better do something like this?

 $node->term_alias 

Of course. This is what it's about node mapping. We map our custom property term_alias to a real property in the json, in this case field_character->{'#items'}[0]->taxonomy_term->path->alias to print its value. And this is done trough the configuration file of this package. Thus, let's publish it with:

lando artisan vendor:publish

and choosing the right option for our backdrop-headless-client package.

Node mapping configuration

Once the configuration file is published, you'll find it in the config folder of your laravel, named as backdrop-headless-client.php.

That config file return an array of configurations. The first element just gets the Backdrop api server url from your .env file. If we want to map our nodes we need to create a second property named node_types as an array of the type of nodes to map:

'node_types' => [
  'post' => [
  ],
  'page' => [
  ],
  ...
],

Each node type is an array of custom properties we want to map to a real value in the JSON. In our example this will be term_alias and any other custom properties we might need:

'node_types' => [
  'post' => [
    'term_alias' => [
    ],
    'term_name' => [
    ],
    ...
  ],
  'page' => [
  ],
  ...
],

Each of our custom properties needs to be an array with two compulsory properties, type and properties, and a third conditional one, value, depending of the value of type:

'node_types' => [
  'post' => [
    'term_alias' => [
      'type' => '',
      'properties' => [
      ],
    ],
    ...

type is a string and can be either single or multiple, depending of the type of Backdrop field we configured at the backend. For a single field we only need properties to be an array of properties' path to the value we want to map. Thus for our example, we map term_alias as follows:

'node_types' => [
  'post' => [
    'term_alias' => [
      'type' => 'single',
      'properties' => [
        '#items',
        0,
        'taxonomy_term',
        'path',
        'alias',
      ],
    ],
    ...

This way we have configured a map of this:

$node->term_alias

to this JSON value:

JSON->field_character->{'#items'}[0]->taxonomy_term->path->alias

Now, what happens with a multiple value field? We need to add a third property value. Backdrop sends you an array of values for a multiple value, so in a point we need to have two different type of data for the map: the properties' path array to the array and the properties' path to the value. If our term_alias field was multiple, our map configuration would need to be as follows:

'node_types' => [
  'post' => [
    'term_alias' => [
      'type' => 'multiple',
      'properties' => [
        '#items',
      ],
      'value' => [
        'taxonomy_term',
        'path',
        'alias',
      ],
    ],
    ...

This way we have mapped $node->term_alias as an array:

JSON->field_character->{'#items'}

being each value of the array a map to the rest of the path:

->taxonomy_term->path->alias

We would use this in our blade as:

@foreach ($node->term_alias as $alias)
  <p>{{ $alias }}</p>            
@endforeach

Once you understand this, it is very easy to map any data coming from your JSON.

Last thing about this topic: we can map only node end points. Other end points, like views or term, have a much easier JSON structure you can use straight without mapping. Also, mapping is optional. If you don't configure it, this package will serve you with the same properties as the JSON received.

How to use this package in our controller

Let's see what are the methods exposed by this package and how we can use them.

::getNode($type, $id)

We use Backdrop::getNode() to get a mapped node, or not mapped if it's not configured, from the Backdrop server at the end point /api/v2/node/{type}/{id}. It takes two arguments, the type of node and its id. We would use it as follows in a controller:

public function show($id)
    {
        $node = Backdrop::getNode('post', $id);

        return view('post', compact('node'));
    }

::getTerm($vocabulary, $id)

This will get a term from the end point /api/{vocabulary}/term/{id}. It takes two arguments, the vocabulary name and the term id. With a route like this:

Route::get('/term/{id}', "PagesController@term");

Our controller would be:

public function term(Request $request, $id)
{
  $vocabulary = $request->segment(1);
  $term = Backdrop::getTerm($vocabulary, $id);
  ...
}

Pointing the browser to yoursite.com/term/1/tags will give us the term with the id 0f 1 for the vocabulary tags.

::getView($view, $display_id, $args = null)

This will get a view from our Backdrop site at the end point /api/v2/views/{view_name}/{display_id}{arguments} and it takes three arguments. The $view argument is the machine name of our view. $display_id is the display machine name of one of the views' display. The third argument $args is optional. I'll talk about it later. Let's use this method in its easiest way.

This will get you the display page of your view posts:

$view = Backdrop::getView('posts', 'page');

Quite straightforward. Just iterate $view in your blade file to print each of the items. But, how about paging your view? You would need the third argument. Also, if your Backdrop's view has one or more exposed filters, you could query them with this third argument. As well, you would have to use it to send any required contextual arguments for your view.

Pagination

When you have a paginated view in your Backdrop server site, you can print your can use laravel's functions for pagination to print paginated data. Let's create a route to show a paginated view in your web.php file:

Route::get('/', "PagesController@index");

This would be the controller:

public function index(Request $request)
{
  $page = $request->get('page', 1);
  $args = '?page=' . ($page - 1);
  $view = Backdrop::getView('posts', 'page', $args);
  $paginator = new LengthAwarePaginator($view['results'], $view['total_items'], $view['items_per_page'], $page);
  return view('home', compact('paginator'));
}

In your blade file you would iterate thru the views' data with:

@foreach ($paginator as $item)
 ...
@endforeach

And print the pagination links at the bottom with:

$paginator->links()

When you point you browser to yoursite.com/ you will get the first page of your view. Whenever you clic on any of the pagination links, you would be sent to a page, for instance, to yoursite.com/?page=2. This example will gets you to the second page of your data set. Laravel counts pages starting at 1. Backdrop does it starting at 0. That's why of the line $args = '?page=' . ($page - 1); in the controller.

Exposed filters

If you configured your Backdrop views with exposed filters to filter the result data set, you can pass them to the view with this third argument $args. And you can of course combine them with the page argument. Just add them to your logic in the controller as parameters of a url, something like this:

$page = $request->get('page', 1);
$args = '?page=' . ($page - 1);
$args .= '&<key>=<value>';

Contextual arguments

In the same way, you could add any contextual arguments expected by your Backdrop view. Remember that Backdrop read contextual arguments in the path part of the url, which need to be added to the $args variable before the parameters. It could be something like this:

$page = $request->get('page', 1);
$args = '<first_contextual_argument>';
$args .= '/<second_contextual_argument>';
$args .= '?page=' . ($page - 1);
$args .= '&<key>=<value>';

You only need to add forward slashes to separate de contextual arguments.

What's next

I'm still working with the Backdrop headless client. I would like, for instance, to add a method to get paragraphs, which the headless provides an end point for. I want to add tests and improve its documentation, also, although it is already usable.

Conclusion

This is just a set of tools to create a site I found very useful. It gives me the best of a BFFFackdrop site with the power and flexibility of laravel to build a frontend. And it might be of help to others.

Also, I don't want to end this blog post without thanking @serundeputy for the great job has done with the bakcdrop headless module, for make me discover lando and helping me every time I had a problem with it, and the great job is doing with Backdrop. I'm a much better web developer thanks to his endless willingness to help.

Finally, I'll be more than happy to get your views on this approach and any ideas or issues you might find. You are very welcome to use the issue queue at github to comment on this package.

backdrop laravel rest api English php composer
You might enjoy

La història del gestor de continguts Backdrop

En aquesta segona píndola explico com, quan i perquè va sorgir Backdrop, un gestor de continguts derivat de Drupal. Aquesta píndola inicia u...