Introduction

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 blog

Starter

You may want to directly try the result of this tutorial. Well, we made a starter out of it so give it a try!

Goal

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

Prerequisites

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.

Setup

Create a blog-strapi folder and get inside!

  • mkdir blog-strapi && cd blog-strapi

Back-end setup

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

Strapi Admin creation

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.

Front-end setup

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

  • Create a 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:

  • Create a .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

  • Import VueApollo and the apolloClient you created in your vue app by adding the following code in your 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.

  • Add UIkit css and js in your 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!

  • Replace the generated code by Vue inside your 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>
  • Remove the components/HelloWorld.vue component

Perfect! 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!

Designing the data structure

Finally! We are now going to create the data structure of our article by creating an Article content type.

  • Dive in your Strapi admin panel and click on the Content Type Builder link in the sidebar.

Strapi Content Type Builder

  • Click on Create new content-type and call it article.

Strapi - add a content type

Now you'll be asked to create all the fields for your content-type:

Strapi - add fields to content type

  • Create the following ones:
    • 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.

  • Click on the Roles & Permission and click on the public role.
  • Check the article find and findone routes and save.

Strapi opening access

Awesome! You should be ready to create your first article right now and fetch it on the GraphQL Playground.

  • Now, create your first article !

Here's an example:
Strapi example of article

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.

GraphQL playground

Create categories

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.

  • Create a category content type with the following field
    • name with type Text

Press save!

  • Create a new field in the Article content type which is a Relation Category has many Articles like below:

Strapi relational fields

  • Click on the Roles & Permission and click on the 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.

Category on right side

Now that we are good with Strapi let's work on the frontend part!

Create the routing of the application

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.

Create the Nav component

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

  • Create a 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

  • Import your Nav component and use it inside your 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 ;)

Create the Articles page

let's display your articles from Strapi now!

  • Create a 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
        }
      }
    }
  `
}
  • Create a 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 /

  • Modify your VueRouter instance by adding the following code inside your 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.

  • Add a 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>  
...

Strapi Blog Vue homepage

Article page

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!

  • Create a 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

  • Configure a new route in your 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!

Article page

Categories

Let's create a page for each category now!

  • Create a 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

  • Configure a new route in your main.js file
const 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!

Category List

Awesome! You can now navigate through categories :)

Conclusion

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

Write for the community

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

News in your inbox

Did you enjoy this article? Subscribe to get the latest posts and the most important updates!