In part 1 we looked at same-origin POST requests from a Vue.js front-end to a Drupal 9 back-end. The app was a basic form for testing, just 2 inputs with a submit button with no bells and whistles. Even though this was a great step in this series, I did this because doing cross-origin POST requests really turned out to be a huge issue so I used same-origin testing to make sure the code was working.
This article is the fast lane to getting this working so you don't need to go through the half-day of chasing down the solution that I did.
In this article, we will look at what needs to be done to get Drupal 9 to work with cross-origin POST requests, look at adding some validation checks in the Vue.js app, and posting a few other properties to the article content type endpoint so we can look at the JSON:API a bit more closely. Let's get started.
Enabling POST Requests
Even though Drupal has the CORS configuration file as we have used in the first part of this series and also in the GET request article it seems that with POST requests cross-origin we need to enable the Access-Control-Allow- origin, headers, and method headers at the virtual host configuration level. You will need to have root access to the server to get this working and edit the virtual host conf file.
Note: I also tested adding the script to a .htaccess in the root of the server and the root of the Drupal instance and this did not work even though some resources said that it should. It may have been my bad in that I was placing the block in the wrong place but since the server conf file worked I investigated this no further.
Below is the snippet of code you need to add to the virtual host, in this particular case I have set up SSL so it is in the :443 port section and I have placed it in the directory section.
On an Ubuntu server, this can be found in the /etc/apache2/sites-available
directory and the file will be a .conf file.
Once you have added the below ifModule mod_headers.c
code to the directory section make sure to reload the service sudo service apache2 reload
<VirtualHost *:443>
# Other virtual host settings
<Directory /var/www/example.com/public_html>
AllowOverride All
<IfModule mod_headers.c>
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Headers "api-key, origin, content-type, x-csrf-token, authorization, accept, x-requested-with, access-control-allow-origin"
Header always set Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"
</IfModule>
</Directory>
# Other virtual host settings
</VirtualHost>
If you need any more information you can check Enable CORS website https://enable-cors.org/server_apache.html
As far as the COR config in the services.yml file we don't need to touch it. In fact if you skipped step 1 you don't really need to copy the services.yml file and enable CORS for your Drupal site now that it is done at the server level.
Move the App to a Remote domain
Now move the app to a remote domain if at all possible, I am using a free Netify host but you can use your localhost if you like.
The main thing is if you restrict Access-Control-Allow-Origin
to certain domains and not "*" wild card, that you have the correct domain in your apache virtual host configuration file.
Header always set Access-Control-Allow-Origin "https://simonramsay.net"
You can only have one domain name in the rule, comma-separated doesn't work.
That's all we need to do to get this working cross-origin. Test it out and see your hard work pay off.
Make sure you are not logged into the site or if you are you are using a private browser window.
Adding Validation
Titles are mandatory in Drupal content types by default so if you submit the form without a value in the title input Drupal will return an error; a 422 Unprocessable Entity error.
If you have any of the other fields set up as mandatory you will also need to make them mandatory on the front-end. The easiest fix for this is to make sure that the value isn't empty on submit and alert the user.
How to: Adding Fields to a Drupal site.
We can do this in Vue.js by adding a function in the methods of our app like so.
methods: {
createPost() {
if (!this.formData.data.attributes.title) {
alert('Please add a title');
} else {
this.submitPost();
}
},
submitPost() {
// Add Submit Function here.
}
}
If you want to return a value if the field is empty and use that to warn the user then that is possible, maybe you could return a list of inputs that are empty and set a class so that the inputs can be styled. I will look at better validation in an upcoming article but for now, this works, we can't submit due to the check for the title being empty returning true.
Okay, now we can't submit an empty form so I will now submit a few more values in the POST request. We will hard code them in this step. However, you could add elements to the form to make them changeable by the user submitting the form.
POST Author information to the API
Let's have a look at adding a relationship to the JSON object in the post request so we can add the author.
{
"data": {
"type": "node--article",
"attributes": {
"title": "Article by User",
"body": {
"value": "This is the body.",
"format": "plain_text"
}
},
"relationships": {
"uid": {
"data": {
"type": "user--user",
"id": "8087783b-5dcf-4e11-a6c1-c39b0e07da0b"
}
}
}
}
}
To see a complete object you can look at the API endpoint for a node and you will see all the properties with the values you can submit. For more on the endpoint, you can read about it in Enabling Drupal 9 as a Headless CMS.
You will need to know that the author is actually an author otherwise it won't work and we need to use the UUID that can be found on the API endpoint and is a random number generated on user creation.
For this to work dynamically you would need to fetch the user information and then populate the object with the correct information but let's just hard code it for now as we know that the value is correct.
Please also note that the user I am using here as the author is not the same user as the one authorized to post - i.e., the user making post request using the API key is the admin but the author is an authenticated user. I did this more because if I leave it out then the author defaults to the one submitting the article and therefore there would have been no difference in the result.
Okay with the author now being posted let look at submitting one other value, that being the status.
Setting the Article to Unpublished using the Status Property
I have also added the status attribute so that when the article is submitted it is set to false and therefore unpublished, as stated you could add a form element such as a check box or radio button to change the value.
{
"data": {
"type": "node--article",
+ "status": false,
"attributes": {
"title": "My Title",
"body": {
"value": "My Body Text",
"format": "plain_text"
}
}
}
Challenge: Can you fetch the user name from the site on load?
See if you can GET the information from the API on page load. If I get enough interest I will look at doing another article on this shortly. However, if you look at the GET request article you should be able to do it.😉
Conclusion
For full code please check part 1 by using the link at top and adding the code samples from this article.
In this article we have looked at how we need to modify the virtual host to allow cross-origin post requests. Then we looked at what you need to make sure that mandatory fields are validated and that if they are left empty the form is not submitted. Then finally we looked at the structure of the article content type JSON:API schema and added the author and the status to the POST request. In part 3 we will look at a real use-case for this other than building a decoupled back-end, be sure to sign up to the newsletter for Vue.js, Drupal 9, and Front-end tech news, tips, and trick. Thanks for your time, see you next time.