Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Decoupling Drupal with Vue.js
Search
Oliver Davies
June 07, 2019
Programming
2.7k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Decoupling Drupal with Vue.js
Presented at Blue Conf 2019.
Oliver Davies
June 07, 2019
More Decks by Oliver Davies
See All by Oliver Davies
Building Static Websites with Sculpin
opdavies
0
1.7k
Taking Flight with Tailwind CSS
opdavies
0
5.6k
TDD - Test Driven Drupal
opdavies
0
4.4k
Building "Build Configs"
opdavies
0
620
Communities and contribution
opdavies
0
340
Working without Workspace
opdavies
0
370
Things you should know about PHP
opdavies
1
920
An Introduction to Mob Programming
opdavies
0
450
Deploying PHP applications with Ansible, Ansible Vault and Ansistrano
opdavies
0
6.6k
Other Decks in Programming
See All in Programming
プロポーザルを書いてもらう
pvcresin
0
580
AIの中の人になってみる
htkym
0
110
30年振りにコンパイラの定数整数除算を改善した
herumi
9
4.1k
Discordを用いたラボオートメーション関連情報収集の自動化
noguhiro2002
0
410
Hono + Inertia + React で LP を構築した話
oukayuka
2
150
jsmini JavaScript Engine を作ってみた話
yosuke_furukawa
PRO
0
350
AWS DevOps AgentのAzure接続機能を検証して見えた活用法/Use Cases Verified for the AWS DevOps Agent's Azure Connectivity Feature
masakiokuda
1
270
Android CLI
fornewid
0
230
Japan Community Day at Kubecon + CloudNativeCon Japan 2026: Learning Container Privilege Control by Building My Own Low-Level Container Runtime
ternbusty
1
170
go-spidermonkeyでAIエージェントのCode Modeを実装する
syumai
2
1k
私のClaude Code活用法 (個人開発編) - PHPerKaigi mini #4(2026/08/24)
panda_program
1
150
170k Jobs a Day on GKE: Scaling Mercari's CI Platform - and What's Next for AI-Native Development
junyaokabe
0
120
Featured
See All Featured
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
The agentic SEO stack - context over prompts
schlessera
0
870
Code Reviewing Like a Champion
maltzj
528
40k
Building Applications with DynamoDB
mza
96
7.2k
The Pragmatic Product Professional
lauravandoore
37
7.4k
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
640
Unsuck your backbone
ammeep
672
58k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
250
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.4k
sira's awesome portfolio website redesign presentation
elsirapls
0
340
HDC tutorial
michielstock
2
810
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
2
3.8k
Transcript
Decoupling Drupal with Vue.js
My a%empt at... Decoupling Drupal with Vue.js
• PHP and Front End Developer • System Administrator •
Senior Engineer at Inviqa • Part-!me freelancer • Open sourcer • @opdavies • oliverdavies.uk
Drupal Content management system, wri!en in PHP
Vue.js Progressive framework for building user interfaces
What do I mean by decoupling?
Drupal is a full stack CMS
Request ➡ Drupal ➡ Twig ➡ Response
Request ➡ Vue.js ➡ Response
Vue.js ➡ Drupal ➡ Vue.js
Why decouple?
• More flexibility • Different development teams • Security •
Exposing data to mul"ple sources • Aggrega"ng data from mul"ple sources • Back-end applica"on can be swapped out
Example: conference talk submission website
Configuring Drupal
drush pm:enable jsonapi
None
Displaying sessions in Vue.js
Configuring CORS in Drupal
Access to XMLH!pRequest at 'h!p:/ / blueconf.docksal/jsonapi/node/session' from origin 'h!p:/
/localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
# services.local.yml cors.config: enabled: false # Specify allowed headers, like
'x-allowed-header'. allowedHeaders: [] # Specify allowed request methods, specify ['*'] to allow all possible ones. allowedMethods: [] # Configure requests allowed from specific origins. allowedOrigins: ['*'] # 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
# services.local.yml cors.config: enabled: true allowedHeaders: [ 'x-csrf-token', 'authorization', 'content-type',
'accept', 'origin', 'x-requested-with', 'access-control-allow-origin', 'x-allowed-header', '*' ] allowedMethods: ['*'] allowedOrigins: ['http://localhost:8080'] exposedHeaders: true maxAge: false supportsCredentials: true
Configuring Vue.js
None
.env APP_VUE_DRUPAL_URL=http://blueconf.docksal
src/App.vue <script> import _ from 'lodash' import AcceptedSessionsList from '@/components/AcceptedSessionsList'
import SessionForm from '@/components/SessionForm' const axios = require('axios') export default { // ... } </script>
src/App.vue components: { AcceptedSessionsList, SessionForm }
src/App.vue data () { return { loaded: false, sessions: []
} }
src/App.vue mounted () { const baseUrl = process.env.VUE_APP_DRUPAL_URL axios.get(`${baseUrl}/jsonapi/node/session`) .then(({
data }) => { this.loaded = true this.sessions = data.data }) }
src/App.vue computed: { sortedSessions: function () { return _(this.sessions) .sortBy(({
attributes }) => attributes.title) } }
src/App.vue <template> <div id="app" class="antialiased min-h-screen font-sans bg-gray-100 text-black p-12">
<div class="w-full max-w-2xl mx-auto"> <accepted-sessions-list :sessions="sortedSessions" /> <session-form /> </div> </div> </template>
src/components/AcceptedSessionsList.vue props: { sessions: { type: Object, required: true }
}
src/components/AcceptedSessionsList.vue computed: { acceptedSessions: function () { return this.sessions .filter(session
=> this.isAccepted(session)) .value() } }
src/components/AcceptedSessionsList.vue methods: { isAccepted: function ({ attributes }) { return
attributes.field_session_status === 'accepted' } }
src/components/AcceptedSessionsList.vue <template> <div> <h1 class="text-4xl font-semibold mb-2">Sessions</h1> <div v-if="acceptedSessions.length >
0" class="bg-white p-6 rounded-lg border"> <ul class="-mb-3"> <li v-for="{ attributes } in acceptedSessions" :key="attributes.drupal_internal__nid" class="mb-3" > {{ attributes.title }} </li> </ul> </div> </div> </template>
Crea%ng sessions: POSTing back to Drupal
None
None
None
drush user:create api
drush user:role:add api_user api
SessionForm.vue <template> <section class="mt-8"> <form @submit.prevent="submit"> <label class="block mb-4"> Title
<input name="title" type="text" v-model="form.title" required /> </label> <label class="block mb-4"> Abstract <textarea name="title" rows="5" v-model="form.body" required /> </label> <input class="cursor-pointer bg-blue-500 hover:bg-blue-700 focus:bg-blue-700 text-gray-100 px-4 py-2 rounded" type="submit" value="Submit session"> </form> </section> </template>
SessionForm.vue data () { return { form: { body: '',
field_session_status: 'accepted', field_session_type: 'full', title: '' } } },
SessionForm.vue methods: { submit () { const adminUuid = '11dad4c2-baa8-4fb2-97c6-12e1ce925806'
const apiUuid = '63936126-87cd-4166-9cb4-63b61a210632' // ... } }
SessionForm.vue methods: { submit () { // ... const data
= { type: 'node--session', attributes: this.form, relationships: { 'field_speakers': { 'data': { 'id': adminUuid, 'type': 'user--user' } }, 'uid': { 'data': { 'id': apiUuid, 'type': 'user--user' } } } } } }
SessionForm.vue const baseUrl = process.env.VUE_APP_DRUPAL_URL axios({ method: 'post', url: `${baseUrl}/jsonapi/node/session`,
data: { data }, headers: { 'Accept': 'application/vnd.api+json', 'Authorization': 'Basic YXBpOmFwaQ==', 'Content-Type': 'application/vnd.api+json' } })
SessionForm.vue // ... .then(({ data }) => { const title
= data.data.attributes.title this.messages.push(`Session ${title} has been created.`) this.$emit('submitted', data.data) this.form.body = '' this.form.title = '' })
App.vue <session-form @submitted="addSession($event)" /> methods: { addSession: function (session) {
this.sessions.push(session) } }
SessionForm.vue // ... .catch(({ response: { data } }) =>
{ this.errors = _(data.errors).map('detail').value() })
Demo
Thanks! opdavi.es/drupal-vuejs @opdavies oliverdavies.uk