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.
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.
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">© 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.
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