Blog Michael BOUVY
All-in-one Symfony 5 + Vue.js + Vuetify web application
Photo by Victoria Strukovskaya on Unsplash

All-in-one Symfony 5 + Vue.js + Vuetify web application

symfony vuejs vuetify
Published on 2021/06/07

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 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/Issue.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 devsymfony 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.