Как создать блог с помощью Strapi, Nuxt (Vue) и Apollo

Некоторое время назад я размышлял о своих интернет-привычках и, более конкретно, о том, что мне действительно нравится, когда я что-то читаю. Вот что я обычно делаю: Я делаю запрос, а затем просто позволяю себе руководствоваться наиболее интересными ссылками. Я всегда обнаруживаю, что читаю сообщения в блоге о чьем-то опыте, который совершенно не связан с запросом, который я первоначально набрал.

Блоггинг – это отличный способ поделиться опытом, убеждениями или отзывами. А Strapi поможет вам создать свой блог! Итак, я почти уверен, что теперь вы понимаете, о чем этот пост. Давайте узнаем, как создать блог с помощью вашей любимой техники – Strapi.

Цель

Если вы знакомы с нашим блогом, то наверняка видели, что мы выпустили серию туториалов о том, как создавать блоги с помощью Strapi с большим количеством frontend-фреймворков:

  • React
  • Next.js
  • Vue.js
  • Gatsby

Цель этой статьи – создать сайт блога, используя Strapi в качестве бэкенда, Nuxt для фронтенда и Apollo для запроса API Strapi с помощью GraphQL.

Нажмите здесь, чтобы получить доступ к исходному коду на GitHub.

Предварительные условия

Чтобы следовать этому руководству, вам потребуется установить последнюю версию Strapi и Nuxt на ваш компьютер, но не волнуйтесь, мы будем устанавливать их вместе!

Вам также потребуется установить Node.js v14 и это все.

Шаг 1: Настройка бэкенда

Начиная с версии beta.9, у нас есть замечательный пакет create strapi-app, который позволяет создать проект Strapi за считанные секунды без необходимости глобальной установки Strapi, так что давайте попробуем его.

  • Создайте папку blog-strapi и заходите внутрь!
    mkdir blog-strapi
    cd blog-strapi
Вход в полноэкранный режим Выход из полноэкранного режима
  • Создайте папку бэкенда Strapi, используя шаблон blog; скопируйте и вставьте в терминал следующую командную строку:
    yarn create strapi-app backend --quickstart --no-run
Войти в полноэкранный режим Выйти из полноэкранного режима

Эта единственная командная строка создаст все необходимое для вашего бэкенда. Не забудьте добавить флаг --no-run, поскольку он предотвратит автоматический запуск сервера вашим приложением.
SPOILER ALERT: сначала нам нужно установить несколько потрясающих плагинов Strapi.

Теперь, когда вы знаете, что нам нужно установить несколько плагинов для улучшения вашего приложения, давайте установим один из наших самых популярных – плагин GraphQL:

    cd backend
    yarn strapi install graphql
    yarn develop
Вход в полноэкранный режим Выход из полноэкранного режима

Откройте ваш Strapi dev-сервер по адресу http://localhost:1337.

После завершения установки вы можете запустить ваш Strapi dev-сервер и создать первого пользователя-администратора. Именно он будет иметь все права в вашем приложении, поэтому, пожалуйста, убедитесь, что вы ввели правильный пароль; (password123) действительно небезопасен.

Отлично! Теперь, когда Strapi готов, вы собираетесь создать свое приложение Nuxt.

Шаг 2: Настройка фронтенда

Ну что ж, самая простая часть завершена, давайте испачкаем руки, разрабатывая наш блог!

  • Создайте проект Nuxt, выполнив следующую команду внутри ./blog-strapi:
    yarn create nuxt-app frontend
Войти в полноэкранный режим Выйти из полноэкранного режима

Примечание: Терминал запросит некоторые подробности о вашем проекте. Поскольку они не имеют отношения к нашему блогу, вы можете их проигнорировать. Однако я настоятельно советую вам прочитать документацию. Так что вперед, наслаждайтесь и жмите Enter до конца!

И снова, как только установка закончится, вы можете запустить свое внешнее приложение, чтобы убедиться, что все в порядке.

    cd frontend  
    yarn dev
Вход в полноэкранный режим Выход из полноэкранного режима
  • Откройте ваш сервер Nuxt.js dev по адресу http://localhost:3000/.

Поскольку вы, возможно, хотите, чтобы люди читали ваш блог или сделать его “милым и красивым”, мы будем использовать популярный CSS фреймворк для стилизации: UIkit и Apollo GraphQL для запроса Strapi с помощью GraphQL.

Шаг 3: Запрос к Strapi с помощью GraphQL

Убедитесь, что вы находитесь в папке frontend перед выполнением следующих команд.

  • Установите все необходимые зависимости для Apollo, выполнив следующую команду:
    // Ctrl + C to close Nuxt.js process
    yarn add @nuxtjs/apollo
Войти в полноэкранный режим Выйти из полноэкранного режима

Apollo Client – это полнофункциональный кэширующий GraphQL-клиент с интеграциями для Vue, React и др. Он позволяет легко создавать компоненты пользовательского интерфейса, которые получают данные через GraphQL.

  • Добавьте @nuxtjs/apollo в раздел modules с конфигурацией Apollo в ./frontend/nuxt.config.js.
    // nuxt.config.js

    export default {
      modules: [
        '@nuxtjs/apollo',
      ],

      apollo: {
        clientConfigs: {
          default: {
            httpEndpoint: process.env.BACKEND_URL || "http://localhost:1337/graphql",
          }
        }
      },
    }
Войдите в полноэкранный режим Выйти из полноэкранного режима

Нам также понадобится использовать переменную env для нашего базового url Strapi, добавьте новую секцию env в конце файла nuxt.config.js:

    // nuxt.config.js

    export default {
      env: {
        strapiBaseUri: process.env.API_URL || "http://localhost:1337"
      },
    }
Вход в полноэкранный режим Выйти из полноэкранного режима

Отлично! Apollo теперь готов. 🚀

Шаг 4: Стилизация с помощью UIkit

UIkit – это легкий и модульный фронтенд-фреймворк для разработки быстрых и мощных веб-интерфейсов.

  • Установите UIkit, выполнив следующую команду:
    yarn add uikit
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь вам нужно инициализировать JS UIkit в вашем приложении Nuxt. Вы сделаете это, создав новый плагин.

  • Создайте файл ./frontend/plugins/uikit.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' }
      ],
    }
Вход в полноэкранный режим Выход из полноэкранного режима

Отлично! Запустите yarn dev для перезапуска вашего сервера и будьте готовы получить впечатление от первой страницы вашего приложения!

Великолепно! Пришло время немного структурировать наш код.

Шаг 5: Проектирование структуры данных

Наконец, мы собираемся создать структуру данных наших статей, создав тип содержимого Article.

  • Зайдите в админ-панель Strapi и нажмите на “Content-Type Builder”.

  • Нажмите на “Создать новый тип коллекции”.

Теперь вам будет предложено создать все поля для вашего типа содержимого

  • Создайте следующие поля:

  • Field Text “title”

  • ПолеRich Text “содержание”

  • Поле Media “изображение”, одно изображение

Нажмите Сохранить! Вот и все, ваш тип содержимого “Статья” создан.

Возможно, вы захотите создать свою первую статью, но перед этим нам нужно сделать одну вещь: Предоставьте доступ к типу контента “Статья”.

  • Нажмите на Настройки, затем Роли и выберите роль “Публичный”.

Отлично! Вы должны быть готовы создать свою первую статью прямо сейчас и получить ее на GraphQL Playground.

Итак, создайте свою первую статью! Вот пример:

Отлично! Теперь вы, возможно, захотите достичь того момента, когда вы действительно сможете получить свои статьи через API! Перейдите на сайт http://localhost:1337/api/articles Разве это не круто!

Вы также можете поиграть с GraphQL Playground.

Возможно, вы захотите назначить категорию для своих статей (новости, тренды, мнения). Вы сделаете это, создав еще один тип контента в Strapi.

Создайте тип контента “Категория” со следующим полем

  • Текст поля “название”

Нажмите сохранить!

Создайте новое поле в типе контента “Статья”, которое является отношением Категория имеет много статей, как показано ниже:

Снова откройте Settings, затем Roles и нажмите на роль “Public”, затем отметьте маршруты категорий find и findone и сохраните.

Теперь вы сможете выбрать категорию для вашей статьи в правом боковом блоке.

Теперь, когда мы хорошо разобрались со Strapi, давайте поработаем над фронтендом!

Шаг 6: Создание макета приложения

Вы можете изменить стандартный макет приложения Nuxt.js, создав свой собственный файл layouts/default.vue.

    <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>
Вход в полноэкранный режим Выход из полноэкранного режима

Как вы можете видеть, список categories пуст. На самом деле, вы хотите иметь возможность выводить список всех категорий в вашей навигационной панели. Для этого нам нужно получить их с помощью Apollo, давайте напишем запрос!

  • Создайте папку apollo/queries/category и внутри нее файл categories.gql со следующим кодом:
    query {
      categories {
        data {
          id
          attributes {
            name
          }
        }
      }
    }
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Замените тег script в файле default.vue следующим кодом:
    <script>
    import categoriesQuery from "~/apollo/queries/category/categories";

    export default {
      data() {
        return {
          categories: {
            data: [],
          },
        };
      },
      apollo: {
        categories: {
          prefetch: true,
          query: categoriesQuery,
        },
      },
    };
    </script>
Вход в полноэкранный режим Выйти из полноэкранного режима

Примечание: Текущий код не подходит для отображения большого количества категорий, так как вы можете столкнуться с проблемой пользовательского интерфейса.

Поскольку эта статья в блоге должна быть короткой, я позволю вам улучшить код, возможно, добавить ленивую загрузку или что-то еще. Пока что ссылки не работают, вы поработаете над этим позже в учебнике 🙂

Шаг 7: Создайте компонент Статьи

Этот компонент будет отображать все ваши статьи на разных страницах, так что перечисление их через компонент – неплохая идея.

  • Создайте файл components/Articles.vue, содержащий следующее:
    <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>
Вход в полноэкранный режим Выход из полноэкранного режима

Как вы видите, вы получаете статьи благодаря GraphQl-запросу, давайте напишем его!

  • Создайте новый файл apollo/queries/article/articles.gql, содержащий следующее:
    query {
      articles {
        data {
          id
          attributes {
            title
            content
            image {
              data {
                attributes {
                  url
                }
              }
            }
            category {
              data {
                attributes {
                  name
                }
              }
            }
          }
        }
      }
    }
Войти в полноэкранный режим Выйти из полноэкранного режима

Потрясающе! Теперь вы можете создать свою главную страницу.

Шаг 8: Индексная страница

Вы хотите перечислить все статьи на вашей индексной странице, давайте воспользуемся нашим новым компонентом! Обновите код в файле pages/index.vue:

    <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>
Войти в полноэкранный режим Выйти из полноэкранного режима

Отлично! Теперь вы достигли момента, когда вы действительно можете получать статьи через GraphQL API!

Вы можете видеть, что при нажатии на статью ничего нет. Давайте вместе создадим страницу статьи!

Шаг 9: Создание страницы статьи

  • Создайте папку pages/articles и внутри нее новый файл _id.vue, содержащий следующее:
    <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>
Войдите в полноэкранный режим Выйти из полноэкранного режима

Здесь вы получаете только одну статью, давайте напишем запрос для нее! Создайте файл apollo/queries/article/article.gql, содержащий следующее:

    query Articles($id: ID!) {
      article(id: $id) {
        data {
          id
          attributes {
            title
            content
            image {
              data {
                attributes {
                  url
                }
              }
            }
            publishedAt
          }
        }
      }
    }
Войти в полноэкранный режим Выйти из полноэкранного режима

Итак, вы можете захотеть отобразить ваш контент в формате Markdown

  • Установите markdownit с помощью yarn add @nuxtjs/markdownit.
  • Установите date-fns с помощью yarn add @nuxtjs/date-fns.
  • Добавьте его в ваши модули внутри вашего файла nuxt.config.js и добавьте конфигурацию объекта markdownit прямо под // nuxt.config.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
      }
    }
Вход в полноэкранный режим Выход из полноэкранного режима
  • Используйте его для отображения содержимого внутри вашего файла _id.vue, заменив строку, отвечающую за отображение содержимого. // pages/articles/_id.vue

Шаг 10: Категории

Давайте теперь создадим страницу для каждой категории! Создайте папку pages/categories и внутри нее файл _id.vue, содержащий следующее:

    <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>
Войдите в полноэкранный режим Выйти из полноэкранного режима

И не забудьте про запрос! Создайте apollo/queries/article/articles-categories.gql, содержащий следующее:

    query Category($id: ID!){
      category(id: $id) {
        data {
          attributes {
            name
            articles {
              id
              data {
                attributes {
                  title
                  content
                  image {
                    data {
                      attributes {
                        url
                      }
                    }
                  }
                  category {
                    data {
                      attributes {
                        name
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
Войти в полноэкранный режим Выйти из полноэкранного режима

Потрясающе! Теперь вы можете перемещаться по категориям 🙂

Заключение

Огромные поздравления, вы успешно справились с этим уроком. Надеюсь, вам понравилось!

Нажмите здесь, чтобы получить доступ к исходному коду на GitHub.

Все еще голодны?

Не стесняйтесь добавлять дополнительные функции, адаптировать этот проект под свои нужды и оставлять свои отзывы в разделе комментариев.

Если вы хотите развернуть свое приложение, ознакомьтесь с документацией.

Оцените статью
Procodings.ru
Добавить комментарий