v2.7 Vuetify Generator

# Install With Docker

If you use the API Platform distribution with Docker, first you have to add the Vue CLI to the Docker image.

In the dev stage of pwa/Dockerfile, add this line:

RUN pnpm install -g @vue/cli @vue/cli-service-global

Then, rebuild your containers.

Delete the content of the pwa\ directory (the distribution comes with a prebuilt Next.js app).

Create a new Vue App and install vuetify and other vue packages:

docker compose exec pwa \
    vue create -d .
docker compose exec pwa \
    vue add vuetify
docker compose exec pwa \
    pnpm install router lodash moment vue-i18n vue-router vuelidate vuex vuex-map-fields

Update the entrypoint:

// client/src/config/entrypoint.js
export const ENTRYPOINT = 'https://localhost:8443';

Update the scripts part of the new package.json:

  "scripts": {
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "start": "vue-cli-service serve --port 3000 --https"
  },

Rebuild the docker containers again to install the Vue App and start the vue server.

Generate the vuetify components with the following command:

docker compose exec pwa \
    pnpm create @api-platform/client -g vuetify --resource book

Omit the resource flag to generate files for all resource types exposed by the API.

Continue by generating the VueJS Web App.

# Install Without Docker

Create a Vuetify application using Vue CLI 3:

vue create -d vuetify-app
cd vuetify-app
vue add vuetify

Install the required dependencies:

npm install router lodash moment vue-i18n vue-router vuelidate vuex vuex-map-fields

In the app directory, generate the files for the resource you want:

npm init @api-platform/client -g vuetify https://demo.api-platform.com src/

Replace the URL with the entrypoint of your Hydra-enabled API. You can also use an OpenAPI documentation with -f openapi3.

Omit the resource flag to generate files for all resource types exposed by the API.

# Generating the VueJS Web App

The code is ready to be executed! Register the generated routes:

// src/router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import bookRoutes from './book';
import reviewRoutes from './review';

Vue.use(VueRouter);

export default new VueRouter({
  mode: 'history',
  routes: [bookRoutes, reviewRoutes]
});

Add the modules to the store:

// src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import makeCrudModule from './modules/crud';
import notifications from './modules/notifications';
import bookService from '../services/book';
import reviewService from '../services/review';

Vue.use(Vuex);

const store = new Vuex.Store({
  modules: {
    notifications,
    book: makeCrudModule({
      service: bookService
    }),
    review: makeCrudModule({
      service: reviewService
    })
  },
  strict: process.env.NODE_ENV !== 'production'
});

export default store;

Update the src/plugins/vuetify.js file with the following:

// src/plugins/vuetify.js
import Vue from 'vue';
import Vuetify from 'vuetify/lib';

Vue.use(Vuetify);

const opts = {
  icons: {
    iconfont: 'mdi'
  }
};

export default new Vuetify(opts);

The generator comes with an i18n feature to allow quick translations of some labels in the generated code, to make it work, you need to create this file:

// src/i18n.js
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import messages from './locales/en';

Vue.use(VueI18n);

export default new VueI18n({
  locale: process.env.VUE_APP_I18N_LOCALE || 'en',
  fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
  messages: {
    en: messages
  }
});

Update your App.vue:

<!-- App.vue -->
<template>
  <v-app id="inspire">
    <snackbar></snackbar>
    <v-navigation-drawer v-model="drawer" app>
      <v-list dense>
        <v-list-item>
          <v-list-item-action>
            <v-icon>mdi-home</v-icon>
          </v-list-item-action>
          <v-list-item-content>
            <v-list-item-title>Home</v-list-item-title>
          </v-list-item-content>
        </v-list-item>
        <v-list-item>
          <v-list-item-action>
            <v-icon>mdi-book</v-icon>
          </v-list-item-action>
          <v-list-item-content>
            <v-list-item-title>
              <router-link :to="{ name: 'BookList' }">Books</router-link>
            </v-list-item-title>
          </v-list-item-content>
        </v-list-item>
        <v-list-item>
          <v-list-item-action>
            <v-icon>mdi-comment-quote</v-icon>
          </v-list-item-action>
          <v-list-item-content>
            <v-list-item-title>
              <router-link :to="{ name: 'ReviewList' }">Reviews</router-link>
            </v-list-item-title>
          </v-list-item-content>
        </v-list-item>
      </v-list>
    </v-navigation-drawer>
    <v-app-bar app color="indigo" dark>
      <v-app-bar-nav-icon @click.stop="drawer = !drawer"></v-app-bar-nav-icon>
      <v-toolbar-title>Application</v-toolbar-title>
    </v-app-bar>

    <v-main>
      <Breadcrumb layout-class="pl-3 py-3" />
      <router-view></router-view>
    </v-main>
    <v-footer color="indigo" app>
      <span class="white--text">&copy; 2019</span>
    </v-footer>
  </v-app>
</template>

<script>
import Breadcrumb from './components/Breadcrumb';
import Snackbar from './components/Snackbar';

export default {
  components: {
    Breadcrumb,
    Snackbar
  },

  data: () => ({
    drawer: null
  })
};
</script>

To finish, update your main.js:

// main.js
import Vue from 'vue';
import Vuelidate from 'vuelidate';
import App from './App.vue';
import vuetify from './plugins/vuetify';

import store from './store';
import router from './router';
import i18n from './i18n';

Vue.config.productionTip = false;

Vue.use(Vuelidate);

new Vue({
  i18n,
  router,
  store,
  vuetify,
  render: h => h(App)
}).$mount('#app');

Go to https://localhost:8000/books/ to start using your app.

You can also help us improve the documentation of this page.

Made with love by

Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.

Learn more

Copyright © 2023 Kévin Dunglas

Sponsored by Les-Tilleuls.coop