Product
Resources
Maxime Castres
February 13, 2020
Our community is looking for talented writers who are passionate about our ecosystem (jamstack, open-source, javascript) and willing to share their knowledge/experiences through our Write for the community program.
Blogging is excellent to let you share experiences, beliefs, or testimonials. And Strapi is useful at helping you create your blog! So, I am pretty sure that you now understand what this post is about. Let’s learn how to create a blog with your favorite tech: Strapi.
Vue.js is an open-source model–view–viewmodel front end JavaScript framework for building user interfaces and single-page applications. It was created by Evan You, and is maintained by him and the rest of the active core team members.
You may want to directly try the result of this tutorial. Well, we made a starter out of it so give it a try!
If you are familiar with our blog you must have seen that we've released a series of tutorials on how to make blogs using Strapi with a lot of frontend frameworks: Gatsby Old, Gatsby new, React, Next.js, Nuxt or Angular.
Today, we are going to learn how to do it with Vue.
The goal here is to be able to create a blog website using Strapi as the backend, Vue for the frontend, and Apollo for requesting the Strapi API with GraphQL.
The source code is available on GitHub
To follow this tutorial, you'll need to have Strapi and Vue-cli installed on your computer, but don't worry, we are going to install these together!
This tutorial use Strapi v3.0.0-beta.18.4
You need to have node v.12 installed and that's all.
Create a blog-strapi folder and get inside!
mkdir blog-strapi && cd blog-strapi
So that's the easy part, since the beta.9 we have an awesome package create strapi-app that allows you to create a Strapi project in seconds without needing to install Strapi globally so let's try it out.
(For the tutorial we will use yarn
as your package manager)
yarn create strapi-app backend --quickstart --no-run
.This single command line will create all you need for your back-end. Make sure to add the --no-run
flag as it will prevent your app from automatically starting the server because SPOILER ALERT: we need to install some awesome Strapi plugins.
Now that you know that we need to install some plugins to enhance your app, let's install one of our most popular. The graphql
plugin:
Path: ./backend
yarn strapi install graphql
Once the installation is completed, you can finally start your Strapi server strapi dev
and create your first Administrator. That's the one that has all the rights in your application, so please make sure to enter a proper password (password123) is really not safe...
Don't forget that Strapi is running on http://localhost:1337
Nice! Now that Strapi is ready, you are going to create your Vue application.
Well, the easiest part has been completed, let's get our hands dirty developing our blog!
Vue setup
Install the vue cli by running the following command in your terminal:
yarn global add @vue/cli
Create a Vue frontend
server by running the following command:
vue create frontend
Note: The terminal will prompt for some details about your project. Chose default (babel, eslint)
. Go ahead, enjoy yourself, and press enter all the way!
Once the installation is over, you can start your frontend app to make sure everything went ok.
cd frontend
yarn serve
As you might want people to read your blog or to make it "cute & pretty" we will use a popular CSS framework for styling: UiKit
and also Apollo
to query Strapi with GraphQL:
Dependencies setup
Make sure you are in the frontend
folder before running the following commands:
Apollo setup
Install all the necessary dependencies for apollo by running the following command:
yarn add vue-apollo graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag
vue-apollo.js
file inside your src
folder containing the following code:import { ApolloClient } from "apollo-client";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
// HTTP connection to the API
const httpLink = createHttpLink({
// You should use an absolute URL here
uri: process.env.VUE_APP_GRAPHQL_URL || "http://localhost:1337/graphql"
});
// Cache implementation
const cache = new InMemoryCache();
// Create the apollo client
const apolloClient = new ApolloClient({
link: httpLink,
cache
});
export default apolloClient;
As you can see we are using a VUE_APP_GRAPHQL_URL
env variable, let's create it in a .env
file:
.env
file at the root of your frontend application containing the following line:VUE_APP_STRAPI_API_URL=http://localhost:1337
VUE_APP_GRAPHQL_URL="http://localhost:1337/graphql"
You are going to use the VUE_APP_STRAPI_API_URL
later on. Now let's head to our main.js
file
main.js
:import Vue from "vue";
import App from "./App.vue";
import VueApollo from "vue-apollo";
import apolloClient from "./vue-apollo";
Vue.config.productionTip = false;
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: apolloClient
});
new Vue({
apolloProvider,
render: h => h(App)
}).$mount("#app");
Some explanations:
First you import and tell Vue to use VueApollo
import VueApollo from "vue-apollo";
Vue.use(VueApollo);
Then you import your apolloClient and tell your app to use it too:
import apolloClient from "./vue-apollo";
const apolloProvider = new VueApollo({
defaultClient: apolloClient
});
new Vue({
apolloProvider,
render: h => h(App)
}).$mount("#app");
Great, apollo is ready now!
UIkit setup
UIkit is a lightweight and modular frontend framework for developing fast and powerful web interfaces.
public/index.html
file by adding the following lines:...
<!-- UIkit CSS -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/css/uikit.min.css"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Staatliches"
/>
<!-- UIkit JS -->
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit-icons.min.js"></script>
...
Awesome! It's time to structure our code a little bit!
App.vue
with the following one:<template>
<div id="app"></div>
</template>
<script>
export default {
name: "App"
};
</script>
<style lang="css">
a {
text-decoration: none;
}
h1 {
font-family: Staatliches;
font-size: 120px;
}
#category {
font-family: Staatliches;
font-weight: 500;
}
#title {
letter-spacing: 0.4px;
font-size: 22px;
font-size: 1.375rem;
line-height: 1.13636;
}
#banner {
margin: 20px;
height: 800px;
}
#editor {
font-size: 16px;
font-size: 1rem;
line-height: 1.75;
}
.uk-navbar-container {
background: #fff !important;
font-family: Staatliches;
}
img:hover {
opacity: 1;
transition: opacity 0.25s cubic-bezier(0.39, 0.575, 0.565, 1);
}
</style>
components/HelloWorld.vue
componentPerfect! You should have a blank page now! I know it's sound weird but it means that you did everything well!
Now let's go on Strapi and create our content-types!
Finally! We are now going to create the data structure of our article by creating an Article
content type.
Content Type Builder
link in the sidebar.Create new content-type
and call it article
.Now you'll be asked to create all the fields for your content-type:
title
with type Text (required)content
with type Rich Text (required)image
with type Media (Single image) and (required)published_at
with type date (required)Press Save! Here you go, your first content type has been created. Now you may want to create your first article, but we have one thing to do before that: Grant access to the article content type.
public
role.find
and findone
routes and save.Awesome! You should be ready to create your first article right now and fetch it on the GraphQL Playground.
Here's an example:
Great! Now you may want to reach the moment when you can actually fetch your articles through the API!
Isn't that cool! You can also play with the GraphQL Playground.
You may want to assign a category to your article (news, trends, opinion). You are going to do this by creating another content type in Strapi.
category
content type with the following fieldname
with type TextPress save!
Category has many Articles
like below:public
role. And check the category find
and findone
routes and save.Now you'll be able to select a category for your article in the right sidebox.
Now that we are good with Strapi let's work on the frontend part!
First of all we are going to create the routing of our application using vue-router
Install vue-router
by running the following command in your terminal:
yarn add vue-router
Head to your main.js
file and replace the code by the following one:
import Vue from "vue";
import VueApollo from "vue-apollo";
import VueRouter from "vue-router";
import apolloClient from "./vue-apollo";
import App from "./App.vue";
Vue.use(VueApollo);
Vue.use(VueRouter);
Vue.config.productionTip = false;
const apolloProvider = new VueApollo({
defaultClient: apolloClient
});
const router = new VueRouter({
mode: "history",
routes: [
{
path: "/"
}
]
});
new Vue({
apolloProvider,
router,
render: h => h(App)
}).$mount("#app");
As you can see, we are simply importing vue-router and telling Vue to use it. We then create our routes. The first one is the main page /
and is not using any components yet, we'll do it later.
We will create a Nav that will be present on every page of your application. To do this, we will simply call it in our App.vue
components/Nav.vue
file containing the following code:<template>
<div>
<nav class="uk-navbar-container" uk-navbar>
<div class="uk-navbar-left">
<ul class="uk-navbar-nav">
<li>
<a href="/">Strapi Blog </a>
</li>
</ul>
</div>
<div class="uk-navbar-right">
<ul class="uk-navbar-nav">
<li v-for="category in categories" v-bind:key="category.id">
<router-link
:to="{ path: '/category/' + category.id }"
:key="category.id"
>
{{ category.name }}
</router-link>
</li>
</ul>
</div>
</nav>
</div>
</template>
<script>
import gql from "graphql-tag";
export default {
name: "Nav",
data() {
return {
categories: []
};
},
apollo: {
categories: gql`
query Categories {
categories {
id
name
}
}
`
}
};
</script>
Here, we are defining a categories
array that will be filled with the response of this GraphQL query:
apollo: {
categories: gql`
query Categories {
categories {
id
name
}
}
`
}
Let's use this new component inside our App.vue
component
App.vue
component<template>
<div id="app">
<Nav />
</div>
</template>
<script>
import Nav from "./components/Nav.vue";
export default {
name: "App",
components: { Nav }
};
</script>
<style lang="css">
...
Awesome! You should see your brand new Nav!
Note The current code is not suited to display a lot of categories as you may encounter a UI issue. Since this blog post is supposed to be short, I will let you improve the code to maybe add a lazy load or something.
For now, the links are not working, you'll work on it later on the tutorial ;)
let's display your articles from Strapi now!
src/containers
folder and create a src/containers/Articles.vue
file containing the following code:<template>
<div>
<div class="uk-section">
<div class="uk-container uk-container-large">
<h1>Strapi blog</h1>
<ArticlesList :articles="articles"></ArticlesList>
</div>
</div>
</div>
</template>
<script>
import ArticlesList from "../components/ArticlesList.vue";
import gql from "graphql-tag";
export default {
components: {
ArticlesList
},
data() {
return {
articles: []
};
},
apollo: {
articles: gql`
query Articles {
articles {
id
title
content
image {
url
}
category {
name
}
}
}
`
}
};
</script>
Here we are just creating the page that will use a ArticlesList
component that will display our articles. We will give these articles as a props from the response of this GraphQL query:
apollo: {
articles: gql`
query Articles {
articles {
id
title
content
image {
url
}
category {
name
}
}
}
`
}
components/ArticlesList.vue
file containing the following code:<template>
<div>
<div class="uk-child-width-1-2" uk-grid>
<div>
<router-link
v-for="article in leftArticles"
:to="{ path: '/article/' + article.id }"
class="uk-link-reset"
:key="article.id"
>
<div class="uk-card uk-card-muted">
<div class="uk-card-media-top">
<img :src="api_url + article.image.url" alt="" height="100" />
</div>
<div class="uk-card-body">
<p
id="category"
v-if="article.category"
class="uk-text-uppercase"
>
{{ article.category.name }}
</p>
<p id="title" class="uk-text-large">{{ article.title }}</p>
</div>
</div>
</router-link>
</div>
<div>
<div class="uk-child-width-1-2@m uk-grid-match" uk-grid>
<router-link
v-for="article in rightArticles"
:to="{ path: '/article/' + article.id }"
class="uk-link-reset"
:key="article.id"
>
<div class="uk-card uk-card-muted">
<div class="uk-card-media-top">
<img :src="api_url + article.image.url" alt="" height="100" />
</div>
<div class="uk-card-body">
<p
id="category"
v-if="article.category"
class="uk-text-uppercase"
>
{{ article.category.name }}
</p>
<p id="title" class="uk-text-large">{{ article.title }}</p>
</div>
</div>
</router-link>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
api_url: process.env.VUE_APP_STRAPI_API_URL
};
},
props: {
articles: Array
},
computed: {
leftArticlesCount() {
return Math.ceil(this.articles.length / 5);
},
leftArticles() {
return this.articles.slice(0, this.leftArticlesCount);
},
rightArticles() {
return this.articles.slice(this.leftArticlesCount, this.articles.length);
}
}
};
</script>
Here we are simply displaying our articles by separating them on left and right side for design purpose:
computed: {
leftArticlesCount() {
return Math.ceil(this.articles.length / 5);
},
leftArticles() {
return this.articles.slice(0, this.leftArticlesCount);
},
rightArticles() {
return this.articles.slice(this.leftArticlesCount, this.articles.length);
}
}
We are using the api_url: process.env.VUE_APP_STRAPI_API_URL
in order to display images from Strapi
Now it's time to display this page, remember the route you defined without a component? Let's tell your Vue app to use this containers/Articles
component when you are visiting /
main.js
file:const router = new VueRouter({
mode: "history",
routes: [
{
path: "/",
components: require("./containers/Articles.vue")
}
]
});
One last thing, we need to tell Vue where to place this component.
router-view
component just under your Nav
component inside your App.vue
component:<template>
<div id="app">
<Nav />
<router-view :key="$route.fullPath"></router-view>
</div>
</template>
...
First of all you'll need to install some dependencies:
Install moment
and vue-markdown-it
by running the following command:
yarn add moment vue-markdown-it
You can see that if you click on the article, there is nothing. Let's create the article page together!
containers/Article.vue
file containing the following:<template>
<div>
<div
v-if="article.image"
id="banner"
class="uk-height-small uk-flex uk-flex-center uk-flex-middle uk-background-cover uk-light uk-padding"
:data-src="api_url + article.image.url"
uk-img
>
<h1>{{ article.title }}</h1>
</div>
<div class="uk-section">
<div class="uk-container uk-container-small">
<vue-markdown-it
v-if="article.content"
:source="article.content"
id="editor"
/>
<p v-if="article.published_at">
{{ moment(article.published_at).format("MMM Do YY") }}
</p>
</div>
</div>
</div>
</template>
<script>
var moment = require("moment");
import VueMarkdownIt from "vue-markdown-it";
import gql from "graphql-tag";
export default {
data() {
return {
article: {},
moment: moment,
api_url: process.env.VUE_APP_STRAPI_API_URL,
routeParam: this.$route.params.id
};
},
components: {
VueMarkdownIt
},
apollo: {
article: {
query: gql`
query Articles($id: ID!) {
article(id: $id) {
id
title
content
image {
url
}
published_at
}
}
`,
variables() {
return {
id: this.routeParam
};
}
}
}
};
</script>
Here we are fetching the url id with routeParam: this.$route.params.id
and setting it in our GraphQL variables:
variables() {
return {
id: this.routeParam
};
}
Now we simply need to configure the router in our main.js
file
main.js
file...
const router = new VueRouter({
mode: "history",
routes: [
{
path: "/",
components: require("./containers/Articles.vue")
},
{
path: "/article/:id",
components: require("./containers/Article.vue")
}
]
});
...
Click on any article!
Let's create a page for each category now!
containers/Category.vue
file containing the following code:<template>
<div>
<div class="uk-section">
<div class="uk-container uk-container-large">
<h1>{{ category.name }}</h1>
<ArticlesList :articles="category.articles || []"></ArticlesList>
</div>
</div>
</div>
</template>
<script>
import ArticlesList from "../components/ArticlesList";
import gql from "graphql-tag";
export default {
data() {
return {
category: [],
routeParam: this.$route.params.id
};
},
components: {
ArticlesList
},
apollo: {
category: {
query: gql`
query Category($id: ID!) {
category(id: $id) {
name
articles {
id
title
content
image {
url
}
category {
id
name
}
}
}
}
`,
variables() {
return { id: this.routeParam };
}
}
}
};
</script>
The code is pretty similar to the previous containers/Article.vue
file. We are fetching articles depending on the category we are in the url:
apollo: {
category: {
query: gql`
query Category($id: ID!) {
category(id: $id) {
name
articles {
id
title
content
image {
url
}
category {
id
name
}
}
}
}
`,
variables() {
return { id: this.routeParam };
}
}
}
Again we simply need to configure the router in our main.js
file
main.js
fileconst router = new VueRouter({
mode: "history",
routes: [
{
path: "/",
components: require("./containers/Articles.vue")
},
{
path: "/article/:id",
components: require("./containers/Article.vue")
},
{
path: "/category/:id",
components: require("./containers/Category.vue")
}
]
});
Click on any Category on your Nav!
Awesome! You can now navigate through categories :)
Huge congrats, you successfully achieved this tutorial. I hope you enjoyed it!
Still hungry?
Feel free to add additional features, adapt this project to your own needs, and give your feedback in the comments section.
If you want to deploy your application, check our documentation: https://strapi.io/documentation/3.0.0-beta.x/guides/deployment.html
Contribute and collaborate on educational content for the Strapi Community https://strapi.io/write-for-the-community
Can't wait to see your contribution!
One last thing, we are trying to make the best possible tutorials for you, help us in this mission by answering this short survey https://strapisolutions.typeform.com/to/bwXvhA?channel=xxxxx
Please note: Since we initially published this blog, we released new versions of Strapi and tutorials may be outdated. Sorry for the inconvenience if it's the case, and please help us by reporting it here.
Get started with Strapi by creating a project, using a starter or trying our instant live demo. Also, join our academy to become a Strapi expert, and consult our forum if you have any questions. We will be there to help you.
See you soon!