Enabling Drupal 9 as a Headless CMS to work with Vue.js

Published Oct 21, 2020
Updated Feb 26, 2021
By Simon
Table of contents

One of the reasons I am using Drupal as my CMS, apart from using it since Drupal 6, is because of the Drupal API first approach meaning that you can build decoupled front-ends for you Drupal instance and syndicate your content to them. Read on to find out how to do this with Vue.js and what needs to be done with your Drupal site to make it all happen.

Prepare Your Drupal API

The first thing I did was to install the JSON:API in Drupal. Since JSON:API has been included with core from Drupal 8.7 it is as easy as checking a check box and clicking install on the extend page of the administration backend of your Drupal site.

drupal extend page in the backend administration.

If you want to you can view the JSON API for this site at the public endpoint URL but if you try to consume the API resource from an dis-allowed origin you will get a big fat no. We will fix this later but lets set up Vue.js

The endpoint for the JSON API for all Drupal sites is at /jsonapi e.g. https://designkojo.com/jsonapi but due to Drupal's default access permissions most endpoints will return "Some resources have been omitted because of insufficient authorization" and "The current user is not allowed to GET the selected resource."


Create Your Vue.js App

I am going to assume you are familiar with creating a Vue.js app using the Vue CLI by running the vue create my-project command. If not I will cover this in another article soon, sign up to my newsletter and be first to know when it hits the internet or check the Vue CLI the documentation. In this example, I am using Vue.js 2 as I had an old Vue.js App I had created last year.

To get the Drupal API end-point data we will use Axios. Axios is a promise based HTTP client which make such tasks easy from the browser. From in your App directory, we can use npm install axios. This will add the package to the node_modules directory and also add it to the dependencies section in the package.json.

With Axios installed we now need to include it in our App, to make things easy we will use the HelloWord.vue component that comes with the default Vue CLI application. HelloWorld.vue can be found in the /src/components directory. In the HelloWorld.vue, in the script section add the import statement import axios from 'axios'. Now we can use axios to get our posts from Drupal.

In the export default block of the Helloworld.vue component, lets add a mounted hook. This hook runs once the App has mounted. In this hook we will use axios to perform a GET request. In the below example we will make a request to our articles https://designkojo.com/jsonapi/node/article endpoint, then we will add the response to our post data. We need to set up our data and add posts as a data value as well.

The mounted hook is one of many hooks we can use in the Vue.js lifecycle, you can read more about the lifescycle on vuejs.org/v2


import axios from 'axios'
export default {
  name: 'HelloWorld',
  mounted() {
      .then(response => this.posts = response.data.data)
      .catch(error => console.log(error))
  data: function() {
    return {
      posts: null


So that is it we can now use {{posts}} in the template section of our component to see our response data in our App. Let's add pre { text-align: left; } in the CSS section to make it easier to read.

But, where is it? The API is set up, the endpoint is working, it seems to respond but still no data rendered on the page. Wait what this? The error Cross-Origin Request Blocked. Okay so there is one more thing to do and that is to enable CORS on Drupal.

CORS cross origin blocked message in firefox development tools

Enable CORS for Drupal

So this is where Drupal shines, by default CORS is disabled so we need to set this up. To do this we need to make a copy of the default.services.yml and save it as service.yml. This is in the site/default/ directory.

Once we have made a copy we need set enabled to true, add allowedHeaders, allowedMethods, and allowedOrigins. Also, don't forget to flush the cache. Below is the modified CORS section of the services.yml which shows simonramsay.net as an allowed origin. simonramsay.net is hosted on netlify at present which was an extremely hassle-free process, more on that in the weeks to come.

   # The above and below are part of the services.yml file that ships with Drupal.
   # I have removed all parameters except CORS and left session.storage.options to show the format of YAML hierarchy and indentation.
   # To see full file please check default.services.yml in the /sites/default directory.
   # *Please note this block is added and highlight like so for this guide only.
   # Configure Cross-Site HTTP requests (CORS).
   # Read https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
   # for more information about the topic in general.
   # Note: By default the configuration is disabled.
    enabled: true
    # Specify allowed headers, like 'x-allowed-header'.
    allowedHeaders: ['content-type']
    # Specify allowed request methods, specify ['*'] to allow all possible ones.
    allowedMethods: ['GET']
    # Configure requests allowed from specific origins.
    allowedOrigins: ['https://simonramsay.net']
    # Sets the Access-Control-Expose-Headers header.
    exposedHeaders: false
    # Sets the Access-Control-Max-Age header.
    maxAge: false
    # Sets the Access-Control-Allow-Credentials header.
    supportsCredentials: false
JSON raw response rendered to the page
JSON data example as would be rendered to the page using <pre>{{posts}}</pre> in our template.

In this article we have looked at installing the JSON:API and enabling CORS on our Drupal site, as well as installing axios in our Vue.js App and rending the response to the App template. In a following post we will look at the JSON:API endpoint in more detail, how to navigate it to get the data we need, and how to render it on our new page. We will also look at switching to Vue.js 3 and NUXTJS to make a truly modern JAM stack app with server side rendering.

Until then mata ne.👋