All-in-one Symfony 5 + Vue.js + Vuetify web application
Nowadays, with the rise of frontend frameworks as Angular, React and Vue.js, headless backends have become more and more popular.
However, creating and mainting two distinct code bases (frontend + backend / API) may quickly become a hassle.
Thanks to Symfony's Webpack Encore, bundling Vue.js inside a Symfony application becomes a breeze. It also provides serious advantages, for instance relying on Symfony's security layer for authentication and session management.
I'll explain step-by-step below how to get an all-in-one Symfony + Vue.js + Vuetify web application.
Install Symfony and requirements
Install Symfony binary (https://symfony.com/download) then:
# Create Symfony application in symfony-vuetify directory
$ symfony new --full symfony-vuetify
$ cd symfony-vuetify
# Install PHP dependencies
$ composer require symfony/webpack-encore-bundle
# Install JS dependencies
$ yarn install
Add the following line in webpack.config.js
:
Encore
...
// Enable Vue loader
.enableVueLoader()
...
;
Run yarn dev
, and install suggested packages:
$ yarn dev
yarn run v1.22.10
$ encore dev
Running webpack ...
Error: Install vue & vue-loader & vue-template-compiler to use enableVueLoader()
yarn add vue@^2.5 vue-loader@^15.9.5 vue-template-compiler --dev
# Install additional packages
$ yarn add vue@^2.5 vue-loader@^15.9.5 vue-template-compiler --dev
Let's now add vue-router and Vuetify:
$ yarn add vue-router@3 vuetify
Edit you assets/app.js
with the following content:
import './styles/app.css';
// start the Stimulus application
import './bootstrap';
import Vue from 'vue'
import VueRouter from 'vue-router'
import vuetify from './plugins/vuetify'
import Home from './components/Home'
const routes = [
{ path: '/', component: Home, name: 'home' }
]
const router = new VueRouter({
mode: 'history',
base: '/app/',
routes
})
Vue.use(VueRouter)
new Vue({
router,
vuetify
}).$mount('#app')
Create the Vuetify plugin in assets/plugins/vuetify.js
:
import Vue from 'vue'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
Vue.use(Vuetify)
const opts = {
theme: {
themes: {
light: {
primary: '#1565c0',
secondary: '#64b5f6',
accent: '#78002e',
error: '#d50000',
},
}
}
}
export default new Vuetify(opts)
Enable Encore's script and link tags in templates/base.html.twig
:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}symfony-vuetify{% endblock %}</title>
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
Create a VueController
in src/Controller/VueController.php
:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class VueController extends AbstractController
{
/**
* @Route("/app/{route}", requirements={"route"=".*"}, name="vue")
*/
public function index(): Response
{
return $this->render('vue/index.html.twig', [
'controller_name' => 'VueController',
]);
}
}
And create the template in templates/vue/index.html.twig
:
{% extends 'base.html.twig' %}
{% block body %}
<div id="app">
<v-app v-cloak>
<v-app-bar short app >Symfony + Vuetify</v-app-bar>
<v-main>
<v-container fluid>
<router-view></router-view>
</v-container>
</v-main>
</v-app>
</div>
{% endblock %}
Note the use of v-cloak
directive, which allows us to hide the Vue.js application during load, with the help of a single CSS line in assets/styles/app.css
:
[v-cloak] { display: none; }
Finally, create the Home
Vue.js component referenced in our router, in assets/components/Home.vue
:
<template>
<v-alert
type="success"
>{{ message }}</v-alert>
</template>
<script>
export default {
name: 'Home',
props: {},
data: () => ({
message: 'Hello Symfony + Vue.js!',
}),
}
</script>
To be able to test your application locally without installing a database (ie. MySQL or PostgreSQL), uncomment the DATABASE_URL
environment variable for SQLite in your .env file and comment-out the PostgreSQL one:
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
# DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=13&charset=utf8"
Run yarn dev
, symfony server:start
, and open http://localhost:8000/app:
You're all set, and can now create additional components in assets/components
. Note that hot-reload unfortunately doesn't work with this setup: although yarn dev --watch
will live-rebuild frontend assets, you'll need to refresh your Symfony application to view changes.
I'm Michael BOUVY, CTO and co-founder of Click&Mortar, a digital agency based in Paris, France, specialized in e-commerce.
Over the last years, I've worked as an Engineering Manager and CTO for brands like Zadig&Voltaire and Maisons du Monde.
With more than 10 years experience in e-commerce platforms, I'm always looking for new challenges, feel free to get in touch!