How to Build a blog using Strapi, Nuxt (Vue) and Apollo
Tags: nuxt, vue, apollo
Published: 2022-06-02 10:57:04


Sometime  ago, I was thinking about my internet habit and, more specifically, what I really like when I'm reading stuff. Here’s what I usually do: I run a query, and then I just let myself be guided by the most interesting links. I always find myself reading blog posts about someone’s experience that is entirely unrelated to the query I initially typed.

Blogging is an excellent way to let share experiences, beliefs, or testimonials. And [Strapi](https://strapi.io/) 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](https://strapi.io/).

## Goal

If you are familiar with our [blog](https://strapi.io/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:

- [React](https://strapi.io/integrations/react-cms)
- [Next.js](https://strapi.io/integrations/nextjs-cms)
- [Vue.js](https://strapi.io/integrations/vuejs-cms)
- [Gatsby](https://strapi.io/integrations/gatsby-cms)

The goal here in this article is to be able to create a blog website using Strapi as the backend, Nuxt for the frontend, and Apollo for requesting the Strapi API with GraphQL.

[Click here to access the source code on GitHub](https://github.com/boulaffasae/sturdy-potato).

## Prerequisites

To follow this tutorial, you'll need to have latest version of Strapi and Nuxt installed on your computer, but don't worry, we are going to install these together!

You'll also need to [install Node.js  v14](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/installation/cli.html#preparing-the-installation) and that's all.

## Step 1: Backend Setup

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.

* Create a blog-strapi folder and get inside!
```bash
    mkdir blog-strapi
    cd blog-strapi
```
* Create your Strapi backend folder using the blog template; copy and paste the following command line in your terminal:
```bash
    yarn create strapi-app backend --quickstart --no-run
```
This single command line will create all you need for your backend. 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 first.*

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:

```bash
    cd backend
    yarn strapi install graphql
    yarn develop
```

Open your Strapi dev server at [http://localhost:1337.](http://localhost:1337/)

![Strapi - Admin](https://paper-attachments.dropbox.com/s_4AF4FDEDF16A3DF3B0654D64C2F33EDD6CA7EA05F0D813E76F8984F96837F085_1639072772822_image.png)


Once the installation is complete, you can finally start your Strapi dev server and create your first admin user. 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 - Homepage](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653127217640_Capture+dcran+de+2022-05-21+11-00-05.png)


Nice! Now that Strapi is ready, you are going to create your Nuxt application.

## Step 2: Frontend Setup

Well, the easiest part has been completed, let's get our hands dirty developing our blog!

* Create a Nuxt project by running the following command inside `./blog-strapi`:
```bash
    yarn create nuxt-app frontend
```
> Note: The terminal will prompt for some details about your project. As they are not really relevant to our blog, you can ignore them. I strongly advise you to [read the documentation](https://github.com/nuxt/create-nuxt-app/blob/master/README.md#features-tada), though. So go ahead, enjoy yourself, and press enter all the way!

Again, once the installation is over, you can start your front-end app to make sure everything is ok.
```bash
    cd frontend  
    yarn dev
```
* Open your Nuxt.js dev server at http://localhost:3000/

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](https://getuikit.com/) and [Apollo GraphQL](https://www.apollographql.com/) to query Strapi with [GraphQL](https://graphql.org/).

## Step 3: Query Strapi with GraphQL

Make sure you are in the `frontend` folder before running the following commands.

* Install all the necessary dependencies for Apollo by running the following command:
```bash
    // Ctrl + C to close Nuxt.js process
    yarn add @nuxtjs/apollo
```
> **Apollo Client** is a fully-featured caching GraphQL client with integrations for Vue, React, and more. It allows you to easily build UI components that fetch data via GraphQL.

* Add `@nuxtjs/apollo` to the modules section with Apollo configuration in `./frontend/nuxt.config.js` 
```js
    // nuxt.config.js
    
    export default {
      modules: [
        '@nuxtjs/apollo',
      ],
    
      apollo: {
        clientConfigs: {
          default: {
            httpEndpoint: process.env.BACKEND_URL || "http://localhost:1337/graphql",
          }
        }
      },
    }
```

We'll also need to use an env variable for our Strapi base url, add a new `env` section at the end of  `nuxt.config.js` file:

```js
    // nuxt.config.js
    
    export default {
      env: {
        strapiBaseUri: process.env.API_URL || "http://localhost:1337"
      },
    }
```

Great! **Apollo** is ready now. 🚀 

## Step 4: Styling with UIkit

[UIkit is a lightweight and modular frontend framework for developing fast and powerful web interfaces.](https://getuikit.com/)

* Install UIkit by running the following command:
```bash
    yarn add uikit
```
Now you need to initialize UIkit's JS in your Nuxt application. You are going to do this by creating a new plugin.

* Create a `./frontend/plugins/uikit.js` file and copy/paste the following code:
```js
    import Vue from 'vue'
    
    import UIkit from 'uikit/dist/js/uikit-core'
    import Icons from 'uikit/dist/js/uikit-icons'
    
    UIkit.use(Icons)
    UIkit.container = '#__nuxt'
    
    Vue.prototype.$uikit = UIkit

Add the following sections to your `nuxt.config.js` file:

    // nuxt.config.js
    
    export default {
     css: [
        'uikit/dist/css/uikit.min.css',
        '@assets/css/main.css'
      ],
    
      plugins: [
        { src: '~/plugins/uikit.js', ssr: false }
      ]
    }

As you can see, you are including both UIkit and `main.css` files! We just need to create the `./frontend/assets/css/main.css` file.

    a {
      text-decoration: none;
    }
    
    h1  {
      font-family: Staatliches;
      font-size: 120px;
    }
    
    #category {
       font-family: Staatliches;
       font-weight: 500;
    }
    
    #title {
      letter-spacing: .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);
    }

**Note:** You don't need to understand what's in this file. It's just some styling ;)

Let's add a beautiful font [Staatliches](https://fonts.google.com/specimen/Staatliches) to the project! Add the following code to your `link` section in your `nuxt.config.js`

    // nuxt.config.js
    
    export default {
      link: [
        { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Staatliches' }
      ],
    }
```

**Perfect!** Run `yarn dev` to restart your server and be prepared to get impressed by the front page of your application!


![Nuxt.js application - http://localhost:3000/](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653128890833_Capture+dcran+de+2022-05-21+11-27-49.png)


**Awesome!** It's time to structure our code a little bit.

## Step 5: Designing the Data Structure

Finally, we are now going to create the data structure of our articles by creating an Article content type. 

* Dive in your Strapi admin panel and click on the “Content-Type Builder”

![Content-Type Builder](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653129086084_Capture+dcran+de+2022-05-21+11-31-17.png)

* Click on “Create new collection type”

![Content-Type Builder - Article](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653129168293_Capture+dcran+de+2022-05-21+11-32-38.png)


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

![Content-Type Builder - Article](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653129217167_Capture+dcran+de+2022-05-21+11-33-22.png)


* Create the following ones:

- Field Text “title”
- FieldRich Text “content” 
- Field Media “image”, single image

**Press Save!** Here you go, your “Article” content type has been created.

![Content-Type Builder - Article](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653129508786_Capture+dcran+de+2022-05-21+11-38-17.png)


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 Settings then Roles and click on the “Public” role

![Settings - Roles](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653129655564_Capture+dcran+de+2022-05-21+11-40-40.png)


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:

![The internet's Own boy](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653129970935_Capture+dcran+de+2022-05-21+11-45-58.png)


Great! Now you may want to reach the moment when you can actually fetch your articles through the API! Go to http://localhost:1337/api/articles Isn't that cool!

You can also play with the [GraphQL Playground](http://localhost:1337/graphql).

![sample](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653130099855_Capture+dcran+de+2022-05-21+11-48-09.png)


You may want to assign a category to your articles (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

- Field Text “name”
![Content-Type Builder - Category](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653130229224_Capture+dcran+de+2022-05-21+11-50-18.png)


Press save!

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

![Content-Type Builder - Article](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653130344151_Capture+dcran+de+2022-05-21+11-52-14.png)


Again, open **Settings** then **Roles** and click on the “Public” role, then check the category `find` and `findone` routes and save.

![Settings - Roles](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653130481053_Capture+dcran+de+2022-05-21+11-54-31.png)


Now you'll be able to select a category for your article in the right sidebox.

![Content Manager - The internet's Own boy](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653130557208_Capture+dcran+de+2022-05-21+11-55-45.png)


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

## Step 6: Create the Layout of the Application

You can change the default layout of the Nuxt.js application by creating your own `layouts/default.vue` file.
```js
    <template>
      <div>
        <nav class="uk-navbar-container" uk-navbar>
          <div class="uk-navbar-left">
            <ul class="uk-navbar-nav">
              <li>
                <a href="#modal-full" uk-toggle
                  ><span uk-icon="icon: table"></span
                ></a>
              </li>
              <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.data" :key="category.id">
                <NuxtLink
                  :to="{ name: 'categories-id', params: { id: category.id } }"
                  >{{ category.attributes.name }}
                </NuxtLink>
              </li>
            </ul>
          </div>
        </nav>
        <div id="modal-full" class="uk-modal-full" uk-modal>
          <div class="uk-modal-dialog">
            <button
              class="uk-modal-close-full uk-close-large"
              type="button"
              uk-close
            ></button>
            <div
              class="uk-grid-collapse uk-child-width-1-2@s uk-flex-middle"
              uk-grid
            >
              <div
                class="uk-background-cover"
                style="
                  background-image: url('https://images.unsplash.com/photo-1493612276216-ee3925520721?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=3308&q=80 3308w');
                "
                uk-height-viewport
              ></div>
              <div class="uk-padding-large">
                <h1 style="font-family: Staatliches">Strapi blog</h1>
                <div class="uk-width-1-2@s">
                  <ul class="uk-nav-primary uk-nav-parent-icon" uk-nav>
                    <li v-for="category in categories.data" :key="category.id">
                      <NuxtLink
                        class="uk-modal-close"
                        :to="{ name: 'categories-id', params: { id: category.id } }"
                        >{{ category.attributes.name }}
                      </NuxtLink>
                    </li>
                  </ul>
                </div>
                <p class="uk-text-light">Built with strapi</p>
              </div>
            </div>
          </div>
        </div>
        <Nuxt />
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          categories: {
            data: [],
          },
        };
      },
    };
    </script>
```

As you can see, `categories` list is empty. In fact, you want to be able to list every category in your navbar. To do this, we need to fetch them with Apollo, let's write the query!

* Create a `apollo/queries/category` folder and a `categories.gql` file inside with the following code:
```
    query {
      categories {
        data {
          id
          attributes {
            name
          }
        }
      }
    }
```

* Replace the `script` tag in your `default.vue` file by the following code:
```js
    <script>
    import categoriesQuery from "~/apollo/queries/category/categories";
    
    export default {
      data() {
        return {
          categories: {
            data: [],
          },
        };
      },
      apollo: {
        categories: {
          prefetch: true,
          query: categoriesQuery,
        },
      },
    };
    </script>
```

> **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 :)**

## Step 7: Create the Articles Component

This component will display all your articles on different pages, so listing them through a component is not a bad idea.

* Create a `components/Articles.vue` file containing the following:
```js
    <template>
      <div>
        <div class="uk-child-width-1-2" uk-grid>
          <div>
            <router-link
              v-for="article in leftArticles"
              :to="{ name: 'articles-id', params: { id: article.id } }"
              class="uk-link-reset"
              :key="article.id"
            >
              <div class="uk-card uk-card-muted">
                <div v-if="article.attributes.image.data" class="uk-card-media-top">
                  <img
                    :src="api_url + article.attributes.image.data.attributes.url"
                    alt=""
                    height="100"
                  />
                </div>
                <div class="uk-card-body">
                  <p
                    id="category"
                    v-if="article.attributes.category.data"
                    class="uk-text-uppercase"
                  >
                    {{ article.attributes.category.data.attributes.name }}
                  </p>
                  <p id="title" class="uk-text-large">
                    {{ article.attributes.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="{ name: 'articles-id', params: { id: article.id } }"
                class="uk-link-reset"
                :key="article.id"
              >
                <div class="uk-card uk-card-muted">
                  <div
                    v-if="article.attributes.image.data"
                    class="uk-card-media-top"
                  >
                    <img
                      :src="api_url + article.attributes.image.data.attributes.url"
                      alt=""
                      height="100"
                    />
                  </div>
                  <div class="uk-card-body">
                    <p
                      id="category"
                      v-if="article.attributes.category.data"
                      class="uk-text-uppercase"
                    >
                      {{ article.attributes.category.data.attributes.name }}
                    </p>
                    <p id="title" class="uk-text-large">
                      {{ article.attributes.title }}
                    </p>
                  </div>
                </div>
              </router-link>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          api_url: process.env.strapiBaseUri,
        };
      },
      props: {
        articles: Object,
      },
      computed: {
        leftArticlesCount() {
          return Math.ceil(this.articles.data.length / 5);
        },
        leftArticles() {
          return this.articles.data.slice(0, this.leftArticlesCount);
        },
        rightArticles() {
          return this.articles.data.slice(
            this.leftArticlesCount,
            this.articles.length
          );
        },
      },
    };
    </script>
```

As you can see, you are fetching articles thanks to a GraphQl query, let's write it!

* Create a new `apollo/queries/article/articles.gql` file containing the following:
```
    query {
      articles {
        data {
          id
          attributes {
            title
            content
            image {
              data {
                attributes {
                  url
                }
              }
            }
            category {
              data {
                attributes {
                  name
                }
              }
            }
          }
        }
      }
    }
```
**Awesome!** Now, you can create your main page.

## Step 8: Index Page

You want to list every article on your index page, let's use our new component! Update the code in your `pages/index.vue` file with:
```js
    <template>
      <div>
        <div class="uk-section">
          <div class="uk-container uk-container-large">
            <h1>Strapi blog</h1>
            <Articles :articles="articles"></Articles>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    import articlesQuery from "~/apollo/queries/article/articles";
    import Articles from "~/components/Articles";
    export default {
      data() {
        return {
          articles: {
            data: [],
          },
        };
      },
      components: {
        Articles,
      },
      apollo: {
        articles: {
          prefetch: true,
          query: articlesQuery,
        },
      },
    };
    </script>
```

Great! You have now reached the moment when you can actually fetch your articles through the GraphQL API!


![Nuxt.js - Homepage](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653132256667_Capture+dcran+de+2022-05-21+12-23-52.png)


You can see that if you click on the article, there is nothing. Let's create the article page together!

## Step 9: Create Article Page

* Create a `pages/articles` folder and a new `_id.vue` file inside containing the following:
```js
    <template>
      <div>
        <div
          v-if="article.data.attributes.image.data"
          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.data.attributes.image.data.attributes.url"
          uk-img
        >
          <h1>{{ article.data.attributes.title }}</h1>
        </div>
        <div class="uk-section">
          <div class="uk-container uk-container-small">
            <div v-if="article.data.attributes.content" id="editor">
              {{ article.data.attributes.content }}
            </div>
            <p v-if="article.data.publishedAt">
              {{ article.data.attributes.publishedAt }}
            </p>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    import articleQuery from "~/apollo/queries/article/article";
    
    export default {
      data() {
        return {
          article: {
            data: [],
          },
          api_url: process.env.strapiBaseUri,
        };
      },
      apollo: {
        article: {
          prefetch: true,
          query: articleQuery,
          variables() {
            return { id: parseInt(this.$route.params.id) };
          },
        },
      },
    };
    </script>
```
Here you are fetching just one article, let's write the query behind it! Create a `apollo/queries/article/article.gql` containing the following:
```
    query Articles($id: ID!) {
      article(id: $id) {
        data {
          id
          attributes {
            title
            content
            image {
              data {
                attributes {
                  url
                }
              }
            }
            publishedAt
          }
        }
      }
    }
```

![](https://media.giphy.com/media/fwDprKZ2a3dqUwvEtK/giphy.gif)


Alright, you may want to display your content as Markdown

- Install `markdownit` with `yarn add @nuxtjs/markdownit`
- Install `date-fns` with `yarn add @nuxtjs/date-fns`
- Add it to your modules inside your `nuxt.config.js` file and add the markdownit object configuration just under `// nuxt.config.js`.

```js    
    export default {
      // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
      buildModules: [
        '@nuxtjs/date-fns',
      ],
    
      // Modules: https://go.nuxtjs.dev/config-modules
      modules: [
        '@nuxtjs/apollo',
        '@nuxtjs/markdownit'
      ],
    
      // [optional] markdownit options
      // See https://github.com/markdown-it/markdown-it
      markdownit: {
        preset: 'default',
        linkify: true,
        breaks: true,
        injected: true
      }
    }
```

- Use it to display your content inside your `_id.vue` file by replacing the line responsible for displaying the content. `// pages/articles/_id.vue`
    
    <div v-if="article.content" id="editor" v-html="$md.render(article.content)"></div>
![Nuxt.js - Article page](https://paper-attachments.dropbox.com/s_12483C53855A83F56330D4447B577BD987D1968F6A4F6C51CC4B36A4E5BF0905_1653311681047_Capture+dcran+de+2022-05-23+14-14-26.png)

## Step 10: Categories

Let's create a page for each category now! Create a `pages/categories` folder and a `_id.vue` file inside containing the following:
```js
    <template>
      <div>
        <client-only>
          <div class="uk-section">
            <div class="uk-container uk-container-large">
              <h1>{{ category.data.attributes.name }}</h1>
              <Articles :articles="category.data.attributes.articles"></Articles>
            </div>
          </div>
        </client-only>
      </div>
    </template>
    
    <script>
    import articlesQuery from "~/apollo/queries/article/articles-categories";
    import Articles from "~/components/Articles";
    export default {
      data() {
        return {
          category: {
            data: [],
          },
        };
      },
      components: {
        Articles,
      },
      apollo: {
        category: {
          prefetch: true,
          query: articlesQuery,
          variables() {
            return { id: parseInt(this.$route.params.id) };
          },
        },
      },
    };
    </script>
```

And don't forget the query! Create a `apollo/queries/article/articles-categories.gql` containing the following:
```
    query Category($id: ID!){
      category(id: $id) {
        data {
          attributes {
            name
            articles {
              id
              data {
                attributes {
                  title
                  content
                  image {
                    data {
                      attributes {
                        url
                      }
                    }
                  }
                  category {
                    data {
                      attributes {
                        name
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
```

**Awesome!** You can now navigate through categories :)

## Conclusion

Huge congrats, you successfully achieved this tutorial. I hope you enjoyed it!

![](http://giphygifs.s3.amazonaws.com/media/b5LTssxCLpvVe/giphy.gif)

[Click here to access the source code  on GitHub](https://github.com/boulaffasae/sturdy-potato).

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 the [documentation](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/deployment.html).

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tl7jgtavowy04diaq903.jpeg)