# Vue Blog Example

In this guide we're going to demonstrate how to quickly build a basic CMS-Powered Blog with Vue.js and Comfortable.

If you're new to Vue.js, we'd recommend checking out this [great Vue.js introduction](https://vuejs.org/v2/guide/) first.

The complete code for this tutorial is [available on GitHub](https://github.com/cmftable/comfortable-vue-blog). You can also explore and play around with it on [CodeSandbox](https://codesandbox.io/s/9jwr8321qr).

## Preparing the CMS – Creating content models

To get started, we'll need a repository for our project and some models for the content. If you don't have a Comfortable account yet, you can [sign up here](https://app.comfortable.io/sign-up). If you already have an account, [log in](https://app.comfortable.io/sign-in). 🙂

Create a new repository for your blog. When you're done, head over to the `Content Types` page to create some models.

![](https://1465026891-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LADWNm2iPQ4ngpcPfGc%2F-LQ8Z_ywNlFuQIMbvBMc%2F-LQ8_oHt79ZpXwB-rqn1%2FUntitled-ff431823-8079-487e-a2a9-3aa46deba4b9.png?alt=media\&token=93209da3-a330-4151-bcfd-90744bb48faf)

### The Author Model

First things first, for any blogpost there is someone who wrote it. Therefore, we're going to create a Content Type `Author`.

On the Content Types page, click the green `+ Add Content Type` button on the top to create a new type `Author`. Make sure you have checked `Create a Collection` field.

![](https://1465026891-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LADWNm2iPQ4ngpcPfGc%2F-LQ8Z_ywNlFuQIMbvBMc%2F-LQ8_vlYVTyNaSAlj4Py%2FUntitled-4cfd99da-1de3-46d2-9aa3-ad26f1a69ec4.png?alt=media\&token=4f563780-380e-4cb6-b06a-9930fec8da7e)

Next, add some fields for this content type. We'll need:

* `Name` – Field Type: `Text` (Single line) *Hint: You can simply rename the* `Title` *field, which is created automatically for new Content Types.*
* `Avatar` – Field Type: `Asset`

### The Blogpost Model

Switch back to the Content Types page and add another type `Blogpost`. Again, make sure to have the field `Create a Collection` enabled. This time, add the following fields:

* `Title` – Field Type: `Text` (Single line) This will be the title for any blogpost
* `Slug` – Field Type: `Text` (Single line) We're going to create a URL for each Blogpost from this field.
* `Image` – Field Type: `Asset` An image to show with each post.
* `Content` – Field Type: `Richtext` This field will contain a Blogposts main content.
* `Author` – Field Type: `Relation` As each Blogpost has an Author, we'll connect both with a relation. In the field configuration, we'll set a one-to-one relation for the field and select the Content Type `Author`, that we have just created.

![](https://1465026891-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LADWNm2iPQ4ngpcPfGc%2F-LQ8Z_ywNlFuQIMbvBMc%2F-LQ8a0WYCD7_0TIOLoac%2FUntitled-fe3700fa-1097-4956-8dc2-4fdc17a01387.png?alt=media\&token=81b663ac-53aa-4298-b53d-251e5f8783a4)

### About Page

The last Content Type we are going to create is really simple. We call it About page in this example, but keep it general so you could create standardised pages from it. Create a new type `Page` and skip the Checkbox `Create a Collection` this time. We need the following fields:

* `Title` – Field Type: `Text` (Single line) This will be the page title
* `Content` – Field Type: `Richtext` This field contains the main content for the page

## Create some content

We need to create some content to display in the example. Let's start with some blog posts. Click the `+` Button on the page header and create a new document of type `Blogpost`.

For the first post, you'll also have to create a new `Author`. You can do while creating a blog post, by simply clicking the button `Create new Entry` on the Author field. For the next posts use `Select Relations` to pick the Author from a list.

For the demo, we'd recommend to have at least 3-5 pieces of content in your blog.

Also, don't forget to add the About page.

### Collections & Pages: The Content Tree

As already mentioned, there is a checkbox `Create a Collection` when creating a new Content Type. This option will add a Collection to the Content Tree for this particular type.

Each collection comes with an individual endpoint, which makes it very easy to retrieve content or change the content output.

You can also link individual pages to the Content Tree and create an endpoint for a single page. We'll do that for the About page. Create your page the same way you created blog posts and when you're done, click the `Add Link` Button above the Content Tree:

![](https://1465026891-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LADWNm2iPQ4ngpcPfGc%2F-LQ8Z_ywNlFuQIMbvBMc%2F-LQ8a5PlbXxXaNXgeZr9%2FUntitled-1893a64f-9394-4a3d-ac75-5140cb0e2186.png?alt=media\&token=f5b653dd-01f7-4038-bb7d-df78dd25b991)

![](https://1465026891-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LADWNm2iPQ4ngpcPfGc%2F-LQ8Z_ywNlFuQIMbvBMc%2F-LQ8a9Vl6V_KiTcJmYCx%2FUntitled-88dcadcf-656d-47f9-b09a-6290dcf0f4a4.png?alt=media\&token=65d09ce7-77a9-4c07-9165-98bd598fa5a6)

Any collection or document node on the Content Tree has an individual endpoint to fetch content. If you're using the SDK, like in this example, all we need is the `API ID`. We'll come back to this later.

### Summary

By the end of this section we've learned:

* How to create Content Types (Models)
* How to create Documents (Content)
* How to create Collections and single linked Documents in the Content Tree

## Installing Vue and Dependencies

We'll use the Vue CLI for this example. Run the following command in your terminal, if you haven't already installed Vue CLI.

`npm install -g @vue/cli`

Next, create the Vue app

`vue create --default comfortable-vue-blog`

Switch to the directory that was created by the CLI

`cd comfortable-vue-blog`

We'll use the Vue router for this project

`vue add router`

The CLI will probably ask you if you'd like use the HTML5 history mode. Usually you'd want to choose 'Yes', and redirect any request to `index.html`. You can read more about the HTML5 History Mode in the [documentation](https://router.vuejs.org/guide/essentials/history-mode.html).

Let's install the [Comfortable JavaScript SDK](https://github.com/cmftable/comfortable-javascript) for a convenient way to interact with the API.

`npm install comfortable-javascript --save`

We'll also use lodash

`npm install lodash --save`

## Coding the app

Enough preparations, start your favourite IDE let's finally start hacking. 😃

*Tip: There are some great IDE extensions for Vue to provide syntax highlighting, autocompletion, etc. We'd highly recommend to check them out.*

### Getting started

`router.js`

Let's prepare the routing with Vue Router first. We'll use the existing base route `/` to display a paginated list of all blogposts and create a new route `/blog/:slug` to display individual posts. If you want to learn more about the Vue Router, you can find their documentation here: <https://router.vuejs.org/>

```jsx
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import BlogPost from './views/BlogPost.vue'
import Page from './views/Page.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/blog/:slug',
      name: 'blog-post',
      component: BlogPost
    },
    {
      path: '/:slug',
      name: 'page',
      component: Page
    }
  ]
})
```

`comfortable.js`

Now we're going to set up the Comforable SDK for the app. Create a new file comfortable.js in `src`:

```javascript
import Comfortable from 'comfortable-javascript';

export const comfortable = Comfortable.api('<Your API ID>', '<Your API KEY>');
```

Import this file into any component you want to use Comfortable.

To find your repositories API ID go to the `Settings` page. You'll also find your API Keys in Settings>API Keys.

`App.vue`

The `App` components purpose is to display a header on top of each page and to provide the `<router-view />` component to display components we've defined in `router.js`.

```jsx
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <main>
      <router-view/>
    </main>
  </div>
</template>

<style>
  @import url('<https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css>');

  #app {
    font-family: 'Avenir', Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
  }

  #nav {
    background-color: #F5F5F5;
    color: #555555;
  }

  #nav a {
    color: #555555;
    text-decoration: none;
    display: inline-block;
    padding: .5rem;
  }

  #nav a:hover {
    text-decoration: underline;
  }

  .content-wrapper {
    max-width: 840px;
    margin: 42px auto;
    padding: 21px;
  }

  a {
    text-decoration: none;
  }
</style>
```

### Display a list of posts

`Home.vue`

On our blog page we want to display all posts and a `Load more` button to paginate the list. Remember we told Vue to load the `Home` Component for the `/` in `router.js`? Now let's have a look at the `Home` Component:

```jsx
<template>
  <div class="home content-wrapper">
    <div v-for="post in posts" :key="post.meta.id">
      <router-link :to="`/blog/${post.fields.slug}`">
        <article>
          <div class="image">
            <img :alt="post.fields.title" :src="`${post.fields.image[0].fields.file.url}?w=840&h=400&fit=crop`">
          </div>
          <h2>{{ post.fields.title }}</h2>
        </article>
      </router-link>
    </div>
    <button v-if="totalPosts > posts.length" @click="getPosts">
      {{loading ? 'Loading...' : 'Load more posts'}}
    </button>
  </div>
</template>

<script>
  import { comfortable } from '@/comfortable.js'

  export default {
    name: 'home',
    data() {
      return {
        posts: [],
        totalPosts: 0,
        loading: false
      }
    },
    methods: {
      getPosts() {
        this.loading = true;

        const options = {
          embedAssets: true,
          offset: this.posts.length
        };

        comfortable.getCollection('blogpost', options)
        .then(result => {
          this.posts.push(...result.data);
          this.totalPosts = result.meta.total;
          this.loading = false;
        })
        .catch(err => {
          this.loading = false;
          throw err;
        })
      }
    },
    created() {
      this.getPosts();
    }
  }
</script>

<style>
  article {
    margin-bottom: 42px;
    text-align: left;
  }

  article .image {
    width: 100%;
  }

  article .image img {
    max-width: 100%;
    height: auto;
  }

  article h2 {
    color: #2d2d33;
  }

  .home article {
    border: 1px solid #ccc;
  }

  .home article h2 {
    margin-left: 21px;
  }
</style>
```

**What's happening here?**

In short: In the script part, we fetch a list of post items from the API. For each post, Vue creates an article and displays its content, wrapped into a link to the single view.

By keeping track of the total number of posts stored in Comfortable, we are able to determine wether to display a `Load more` button. When the button gets clicked, the next set of post items gets fetched from the API.

### Display a single post

Now that we have a list of posts, we want to be able to load single blogposts on a separate page. We've already used the `Slug` field from the `Blogpost` model to build links on the posts list page. As you remember in `router.js`, we have defined the second part of a single view URL to be a route parameter `:slug`. So the piece of information we need to retrieve a single post is present as `this.$route.params.slug`. All we have to do now, is use a filter query.

`BlogPost.vue`

```jsx
<template>
  <div class="post" v-if="post && author">
    <article>
      <div class="image">
        <img :alt="post.fields.title" :src="`${post.fields.image[0].fields.file.url}?w=1680&h=750&fit=crop`">
      </div>
      <div class="content-wrapper">
        <h2>{{ post.fields.title }}</h2>
        <div class="content" v-html="post.fields.content.html"></div>
        <div class="author">
          <img :src="`${author.fields.avatar[0].fields.file.url}?w=30&h=30&fit=crop`" alt="author.fields.name"> Written by {{ author.fields.name }}
        </div>
      </div>
    </article>
  </div>
</template>

<script>
  import Comfortable from 'comfortable-javascript';
  import { comfortable } from '@/comfortable.js'
  import _ from 'lodash';

  export default {
    name: 'blogPost',
    data() {
      return {
        post: null,
        author: null,
      }
    },
    methods: {
      getPost() {
        const options = {
          embedAssets: true,
          includes: 1,
          filters: new Comfortable.Filter()
            .addAnd('slug', 'equal', this.$route.params.slug)
        };

        comfortable.getDocuments(options)
        .then(result => {
          this.post = result.data[0];
          this.author = _.find(result.includes.author, { meta: { id: this.post.fields.author[0].meta.id } });
        })
        .catch(err => {
          throw err;
        })
      }
    },
    created() {
      this.getPost();
    }
  }
</script>

<style>
  .post .image{
    width: 100%;
  }

  .post .image img{
    width: 100%;
  }

  .post .author img {
    margin-top: 10px;
  }

  .post .author img {
    display: inline-block;
    margin-bottom: -8px;
    margin-right: 10px;
    border-radius: 50%;
  }
</style>
```

### The About page

At last we add the About page. We're going to create this page from the basic Content Type `Page`. This way you are able create several pages from the same Component and Content Type.

The important part here is, that we're fetching this page by a route parameter again, and this time we'll use the document alias that we've created in the Content Tree, instead of a filter.

`Page.vue`

```jsx
<template>
  <div class="page" v-if="page">
    <article>
      <div class="content-wrapper">
        <h2>{{ page.fields.title }}</h2>
        <div class="content" v-html="page.fields.content.html"></div>
      </div>
    </article>
  </div>
</template>

<script>
  import { comfortable } from '@/comfortable.js'

  export default {
    name: 'page',
    data() {
      return {
        page: null
      }
    },
    methods: {
      getPage() {
        comfortable.getAlias(this.$route.params.slug)
        .then(result => {
          this.page = result;
        })
        .catch(err => {
          throw err;
        })
      }
    },
    created() {
      this.getPage();
    }
  }
</script>
```

## Running and building the app

That's it 🙂 Run `npm run serve` to view the app in your browser, or `npm run build` to get a deployable blog from your codebase. Don't forget to handle the [HTML5 History Mode](https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations) on your server.

Happy coding!

[Join our Slack team](https://slack-comfortable.herokuapp.com/) to get in touch with the community and ask questions!

## Bonus: Serverless deployment with Netlify

Deploying your Blog with Netlify is perfect if you don't want to handle a server or webspace yourself. You'll be provided with a really fast and reliable deployment and hosting for any static website.

Using Netlify for this example is as simple as clicking this [link](https://app.netlify.com/start/deploy?repository=https://github.com/cmftable/comfortable-vue-blog). You'll be asked to connect your GitHub or GitLab account to let Netlify create a Repository on your behalf. The Repository will include the code from this example and you can change it and play around as you like.

That's all. Your Blog will be available within seconds. 🚀 😊
