Как создать приложение с советами по продуктивности с помощью Render, Remix и Strapi

В этом уроке вы узнаете, как создать приложение с советами по продуктивности с помощью Render, Remix и Strapi.

Приложение для повышения продуктивности будет иметь следующее:

  • Целевая страница с описанием приложения,
  • страница /tips для просмотра всех советов по продуктивности в базе данных, и
  • Страница, на которой отображаются другие подробности совета по продуктивности.

Веб-приложение «Советы по продуктивности» — это приложение, которое отображает список советов по повышению продуктивности. Это приложение работает путем передачи советов и информации о них из базы данных приложения во фронтенд.

Вы можете получить готовый проект в репозитории Strapi и репозитории Remix.

Необходимые условия

Для выполнения этой статьи вам понадобятся следующие знания:

  • JavaScript
  • Основы Remix
  • Основы Strapi

Вам также необходимо установить следующее:

  • Git
  • NodeJS
  • Yarn
  • Npm

Почему Remix?

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

Приложение «Советы по продуктивности» состоит из фронтенда, использующего Remix, и бэкенда, использующего Strapi. Эффективное взаимодействие между частями приложения приводит к улучшению и ускорению работы пользователей.

Почему Strapi?

Strapi — это безголовая CMS с открытым исходным кодом, которая позволяет создавать GraphQL и RESTful API. Безголовая CMS позволяет разработчикам легко создавать API, которые могут быть интегрированы с любым фронтендом.

CMS (система управления содержимым) — это программное обеспечение, которое позволяет пользователям создавать и поддерживать содержимое веб-приложения. Безголовая CMS похожа на обычную CMS, но у нее нет фронтенда, с которым взаимодействуют люди.

С помощью Strapi вы можете создать API, который будет следующим:
  • Легко интегрируется с любым фронтендом,
  • Настраиваемым,
  • простым в обслуживании, и
  • Легко разрабатывается.

Почему Render?

Render — это облачный сервис, который позволяет развертывать и запускать веб-сайты, приложения и многое другое. С помощью Render вы можете настроить и развернуть весь свой проект с помощью одного файла конфигурации. Этот конфигурационный файл служит образцом для настройки вашего проекта.

Чтобы запустить веб-сайт Remix и приложение Strapi, необходимо развернуть их на облаке. После развертывания приложений вы можете получить к ним доступ, используя доменное имя, предоставленное облачным провайдером, или добавить собственное доменное имя.

Приложение Strapi

Прежде чем начать, убедитесь, что у вас есть аккаунт на GitHub, а затем выполните следующие шаги:

  1. Откройте этот репозиторий в браузере.
  2. Нажмите кнопку Использовать этот шаблон, чтобы скопировать репозиторий в свой аккаунт.
  3. Нажмите кнопку Создать репозиторий из шаблона после заполнения любого имени в качестве имени репозитория (вам не нужно включать все ветки).
  4. Клонируйте копию репозитория на локальную машину.
  5. Перейдите к локальному репозиторию в терминале.
  6. Запустите yarn install для установки зависимостей.
  7. Выполните команду cp .env.example .env, чтобы скопировать переменные окружения из .env.example в .env.
  8. Запустите yarn develop, чтобы запустить приложение strapi в режиме разработки.

После настройки и запуска приложения Strapi вам необходимо выполнить несколько действий:

  1. Откройте страницу http://localhost:1337/admin в браузере.
  2. Создайте учетную запись администратора на странице администратора.

В приложении Strapi из репозитория уже настроен тип содержимого совета по продуктивности. Вы можете увидеть его, перейдя в Content Type Builder -> Совет.

Прежде чем использовать API, необходимо добавить примеры продуктивных советов:
  1. Перейдите в Менеджер контента.
  2. Нажмите кнопку Создать новую запись.
  3. Заполните поля и выберите автора.
  4. Нажмите Сохранить.

При желании вы можете добавить больше советов в свое приложение, прежде чем продолжить.

Теперь вы закончили настройку приложения Strapi. Вы можете приступить к созданию фронтенда.

Приложение Remix

Чтобы создать приложение remix, выполните следующие шаги:

  1. Создайте форк этого репозитория на своем аккаунте GitHub.
  2. Клонируйте форкнутый репозиторий на свою локальную машину.
  3. Запустите npm install для установки его зависимостей.

Этот репозиторий разделен на пять (5) ветвей, каждая из которых показывает шаги, предпринятые для создания приложения.

Шаг 1: Инициализация приложения Remix

Ссылка на этот шаг доступна здесь. В этой ветке приложение remix было создано и инициализировано. Когда вы запускаете npm run dev в этой ветке, веб-приложение выглядит так, как показано ниже:

Шаг 2: Добавление стиля

Ссылка на этот шаг доступна здесь.

Чтобы перейти в эту ветку, используйте git checkout step-2. Изменения, внесенные в этом шаге, касались файла app/root.tsx:

1. Импортируйте библиотеку Pico CSS.
    import {
      Link,
      Links,
      LiveReload,
      Meta,
      Outlet,
      Scripts,
      ScrollRestoration,
    } from "remix";

    export function links () {
      return [{
        rel: "stylesheet",
        href: "https://unpkg.com/@picocss/pico@latest/css/pico.min.css"
      }]
    }

    export function meta() {
      return { title: "Productivity Tips" };
    }

    export default function App() {
      return (
        <html lang="en">
          <head>
            <meta charSet="utf-8" />
            <meta name="viewport" content="width=device-width,initial-scale=1" />
            <Meta />
            <Links />
          </head>
          <body>
            <nav style={{marginLeft: 10}}>
              <h1>
                <Link to="/" style={{color: "var(--h1-color)"}}>
                  Productivity Tips
                </Link>
              </h1>
            </nav>
            <Outlet />
            <ScrollRestoration />
            <Scripts />
            <LiveReload />
          </body>
        </html>
      );
    }
Войти в полноэкранный режим Выйти из полноэкранного режима
2. Изменение заголовка страницы
    import {
      Link,
      Links,
      LiveReload,
      Meta,
      Outlet,
      Scripts,
      ScrollRestoration,
    } from "remix";

    export function links () {
      return [{
        rel: "stylesheet",
        href: "https://unpkg.com/@picocss/pico@latest/css/pico.min.css"
      }]
    }

    export function meta() {
      return { title: "Productivity Tips" };
    }

    export default function App() {
      return (
        <html lang="en">
          <head>
            <meta charSet="utf-8" />
            <meta name="viewport" content="width=device-width,initial-scale=1" />
            <Meta />
            <Links />
          </head>
          <body>
            <nav style={{marginLeft: 10}}>
              <h1>
                <Link to="/" style={{color: "var(--h1-color)"}}>
                  Productivity Tips
                </Link>
              </h1>
            </nav>
            <Outlet />
            <ScrollRestoration />
            <Scripts />
            <LiveReload />
          </body>
        </html>
      );
    }
Войдите в полноэкранный режим Выйти из полноэкранного режима
3. Добавить заголовок «Советы по продуктивности» и связать его с /
    import {
      Link,
      Links,
      LiveReload,
      Meta,
      Outlet,
      Scripts,
      ScrollRestoration,
    } from "remix";

    export function links () {
      return [{
        rel: "stylesheet",
        href: "https://unpkg.com/@picocss/pico@latest/css/pico.min.css"
      }]
    }

    export function meta() {
      return { title: "Productivity Tips" };
    }

    export default function App() {
      return (
        <html lang="en">
          <head>
            <meta charSet="utf-8" />
            <meta name="viewport" content="width=device-width,initial-scale=1" />
            <Meta />
            <Links />
          </head>
          <body>
            <nav style={{marginLeft: 10}}>
              <h1>
                <Link to="/" style={{color: "var(--h1-color)"}}>
                  Productivity Tips
                </Link>
              </h1>
            </nav>
            <Outlet />
            <ScrollRestoration />
            <Scripts />
            <LiveReload />
          </body>
        </html>
      );
    }
Войти в полноэкранный режим Выход из полноэкранного режима

Когда вы запустите эту программу, веб-приложение будет выглядеть следующим образом:

Шаг 3: Редактирование содержимого [Добавить советы].

Ссылку на этот шаг можно найти здесь.
Для перехода на эту ветку используйте команду git checkout step-3.

Файл, измененный в этом шаге, — src/index.tsx, и в нем были сделаны следующие изменения:

1. Добавьте параграф с описанием приложения:

    import { Link } from "remix";

    export default function Index() {
      return (
        <main className="container">
          <p>
            Over time everyone develops a Swiss army knife of tips, tricks,
            and hacks to boost productivity. At Render, I created a
            #productivity-tips Slack channel for anyone to share their best
            productivity boosters with everyone on the team. Using 
            <a href="https://strapi.io">Strapi</a> and 
            <a href="https://remix.run">Remix</a>, we made a little web app to
            catalog all of these tips and share them with others. 🤓
          </p>
          <Link to="/tips">👉 Productivity Tips</Link>
        </main>
      );
    }
Вход в полноэкранный режим Выйти из полноэкранного режима

2. Добавьте ссылку на /tips.

    import { Link } from "remix";

    export default function Index() {
      return (
        <main className="container">
          <p>
            Over time everyone develops a Swiss army knife of tips, tricks,
            and hacks to boost productivity. At Render, I created a
            #productivity-tips Slack channel for anyone to share their best
            productivity boosters with everyone on the team. Using 
            <a href="https://strapi.io">Strapi</a> and 
            <a href="https://remix.run">Remix</a>, we made a little web app to
            catalog all of these tips and share them with others. 🤓
          </p>
          <Link to="/tips">👉 Productivity Tips</Link>
        </main>
      );
    }
Войти в полноэкранный режим Выйти из полноэкранного режима

/tips — это страница, которая будет создана на следующем шаге. Эта страница содержит список всех советов, хранящихся в бэкенде Strapi.

Когда вы запустите программу на этом шаге, веб-приложение будет выглядеть так, как показано ниже:

Шаг 4: Создание новых файлов

Ссылку на этот шаг можно найти здесь

Ниже перечислены новые файлы, созданные на этом шаге:

    import { Outlet } from "remix";

    export default function TipsRoute() {
      return (
        <main className="container">
          <Outlet />
        </main>
      );
    }
Вход в полноэкранный режим Выход из полноэкранного режима
    import { Link, useLoaderData } from "remix";
    import { checkStatus, checkEnvVars } from "~/utils/errorHandling";

    export async function loader () {
      checkEnvVars();

      const res = await fetch(`${process.env.STRAPI_URL_BASE}/api/tips?populate=*`, {
        method: "GET",
        headers: {
          "Authorization": `Bearer ${process.env.STRAPI_API_TOKEN}`,
          "Content-Type": "application/json"
        }
      });

      // Handle HTTP response code < 200 or >= 300
      checkStatus(res);

      const data = await res.json();

      // Did Strapi return an error object in its response?
      if (data.error) {
        console.log('Error', data.error)
        throw new Response("Error getting data from Strapi", { status: 500 })
      }

      return data.data;
    }

    export default function Tips() {
      const tips = useLoaderData();

      return (
        <ul>
          {tips.map((tip) => (
            <li key={tip.attributes.Slug}>
              <Link to={tip.attributes.Slug}>{tip.attributes.Name}</Link>
            </li>
          ))}
        </ul>
      );
    }
Войти в полноэкранный режим Выйти из полноэкранного режима
    // Custom error class for errors from Strapi API
    class APIResponseError extends Error {
        constructor(response) {
            super(`API Error Response: ${response.status} ${response.statusText}`);
        }
    }

    export const checkStatus = (response) => {
        if (response.ok) {
            // response.status >= 200 && response.status < 300
            return response;
        } else {
            throw new APIResponseError(response);
        }
    }

    class MissingEnvironmentVariable extends Error {
        constructor(name) {
            super(`Missing Environment Variable: The ${name} environment variable must be defined`);
        }
    }

    export const checkEnvVars = () => {
        const envVars = [
            'STRAPI_URL_BASE',
            'STRAPI_API_TOKEN'
        ];

        for (const envVar of envVars) {
            if (! process.env[envVar]) {
                throw new MissingEnvironmentVariable(envVar)
            }
        }
    }
Войти в полноэкранный режим Выход из полноэкранного режима

В файле index.jsx функция useLoaderData() вызывает функцию loader в строках 4-27. Функция loader полезна для отделения программы, взаимодействующей с API, от представления.

    import { Link, useLoaderData } from "remix";
    import { checkStatus, checkEnvVars } from "~/utils/errorHandling";

    export async function loader () {
      checkEnvVars();

      const res = await fetch(`${process.env.STRAPI_URL_BASE}/api/tips?populate=*`, {
        method: "GET",
        headers: {
          "Authorization": `Bearer ${process.env.STRAPI_API_TOKEN}`,
          "Content-Type": "application/json"
        }
      });

      // Handle HTTP response code < 200 or >= 300
      checkStatus(res);

      const data = await res.json();

      // Did Strapi return an error object in its response?
      if (data.error) {
        console.log('Error', data.error)
        throw new Response("Error getting data from Strapi", { status: 500 })
      }

      return data.data;
    }

    export default function Tips() {
      const tips = useLoaderData();

      return (
        <ul>
          {tips.map((tip) => (
            <li key={tip.attributes.Slug}>
              <Link to={tip.attributes.Slug}>{tip.attributes.Name}</Link>
            </li>
          ))}
        </ul>
      );
    }
Вход в полноэкранный режим Выход из полноэкранного режима

Перед запуском приложения необходимо выполнить следующие шаги:

  1. Откройте приборную панель Strapi.
  2. Перейдите в раздел Настройки —> Токены API —> Создать новый API, чтобы создать токен API.
  3. Нажмите кнопку Сохранить после заполнения необходимых полей.
  4. Скопируйте созданный API-токен.
  5. Скопируйте новое содержимое файла .env.example в .env.
    STRAPI_URL_BASE=http://localhost:1337
    STRAPI_API_TOKEN=a-secret-token-from-the-strapi-admin-gui
Вход в полноэкранный режим Выйдите из полноэкранного режима
  1. Замените a-secret-token-from-the-strapi-admin-gui на токен API, затем нажмите Save.>Если вы используете windows, вам может понадобиться изменить значение STRAPI_URL_BASE на http://127.0.0.1:1337.

После запуска приложения командой npm run dev веб-приложение должно выглядеть так, как показано ниже:

Шаг 5: Определите динамические маршруты и стиль

Вот ссылка на этот шаг

В этом шаге создаются следующие файлы:

В файле $tipId.jsx происходит следующее:

  1. Импортируйте файл tip.css в файл $tipId.jsx.
    import { useLoaderData, Link } from "remix";
    import { checkStatus, checkEnvVars } from "~/utils/errorHandling";

    import stylesUrl from "~/styles/tip.css";

    export function links () {
      return [{ rel: "stylesheet", href: stylesUrl }];
    }


    export function meta ({ data }) {
      return {
        title: data.attributes.Name
      }
    }

    export async function loader ({ params }) {
      ...
Вход в полноэкранный режим Выйдите из полноэкранного режима
  1. Создайте компонент TipRoute, который экспортируется по умолчанию.
    ...
    export default function TipRoute() {
      const tip = useLoaderData();

      return (
        <div>
          <Link to="/tips" style={{ textDecoration: 'none' }}> back to list</Link>
          <hgroup>
            <h2>{tip.attributes.Name}</h2>
            <h3>by {tip.attributes.Author.data?.attributes.firstname ?? 'an unknown user'}</h3>
          </hgroup>

          <p>
            {tip.attributes.Description}
          </p>
          <div className="grid">
            {tip.attributes.Screenshots.data.map((s) => (
              <div key={s.attributes.hash}>
                <img
                  src={s.attributes.formats.thumbnail.url}
                  alt={tip.attributes.Name + ' screenshot'}
                />
              </div>
            ))}
          </div>
        </div>
      );
    }
Войдите в полноэкранный режим Выход из полноэкранного режима
  1. Создайте функцию loader для хука useLoaderData() в компоненте TipRoute.
    import { useLoaderData, Link } from "remix";
    import { checkStatus, checkEnvVars } from "~/utils/errorHandling";

    import stylesUrl from "~/styles/tip.css";

    export function links () {
      return [{ rel: "stylesheet", href: stylesUrl }];
    }

    export function meta ({ data }) {
      return {
        title: data.attributes.Name
      }
    }

    export async function loader ({ params }) {
      checkEnvVars();

      const res = await fetch(`${process.env.STRAPI_URL_BASE}/api/tips`
        + `?populate=*&filters[Slug]=${params.tipId}`, {
        method: "GET",
        headers: {
          "Authorization": `Bearer ${process.env.STRAPI_API_TOKEN}`,
          "Content-Type": "application/json"
        }
      })

      // Handle HTTP response code < 200 or >= 300
      checkStatus(res);

      const data = await res.json();

      // Did Strapi return an error object in its response?
      if (data.error) {
        console.log('Error', data.error)
        throw new Response("Error getting data from Strapi", { status: 500 })
      }

      // Did Strapi return an empty list?
      if (!data.data || data.data.length === 0) {
        throw new Response("Not Found", { status: 404 });
      }


      const tip = data.data[0];

      // For a Tip with no screenshot, replace API returned null with an empty array
      tip.attributes.Screenshots.data = tip.attributes.Screenshots.data ?? [];

      // Handle image URL being returned as just a path with no scheme and host.
      // When storing media on the filesystem (Strapi's default), media URLs are
      // return as only a URL path. When storing media using Cloudinary, as we do
      // in production, media URLs are returned as full URLs.
      for (const screenshot of tip.attributes.Screenshots.data) {
        if (!screenshot.attributes.formats.thumbnail.url.startsWith('http')) {
          screenshot.attributes.formats.thumbnail.url = process.env.STRAPI_URL_BASE +
            screenshot.attributes.formats.thumbnail.url;
        }
      }
      return tip;
    }
    ...
Вход в полноэкранный режим Выход из полноэкранного режима

В функции loader происходит следующее:

  1. Используйте функцию checkEnvVars() для проверки наличия всех необходимых переменных окружения.
  2. Сделайте запрос к маршруту http://localhost:1337/api/tips (URL содержит параметры, которые Strapi использует для уточнения запроса).
  3. Используйте функцию checkStatus(), чтобы проверить, что статус http в порядке (от 200 до 299).
  4. Проверьте, не вернул ли strapi ошибку в своем ответе. Иногда Strapi отвечает объектом с ошибкой при наличии http-статуса OK.
  5. Проверьте, не вернул ли strapi пустой список. Если по id нет ни одного продуктивного совета, Strapi возвращает пустой список.
  6. Обработка маршрутизации для изображений в совете. Strapi использует локальную файловую систему, когда хранит загруженные файлы, и использует облачный сервер. Когда файл загружается с сервера разработки, Strapi не отвечает URL-адресом файла, а только его локальным путем.

Когда вы запустите приложение на этом этапе, страница http://localhost:3000/tips/tip должна выглядеть так, как показано ниже:

Развертывание для рендеринга

Перед развертыванием проекта в облаке необходимо выполнить следующие шаги:

  1. Откройте репозиторий strapiconf2022-workshop-strapi.
  2. Измените значение repos в файле /render.yaml на URL вашего удаленного репо strapi и remix (в вашем случае это может быть https://github.com/your-username/strapiconf2022-workshop-strapi и https://github.com/your-username/strapiconf2022-workshop-remix).
    services:
      - type: web
        name: productivity-tips-api
        env: node
        plan: free
        # Update the following line with your Strapi GitHub repo
        repo: https://github.com/render-examples/strapiconf2022-workshop-strapi
        branch: main
        buildCommand: yarn install && yarn build
        startCommand: yarn start
        healthCheckPath: /_health
        envVars:
          - key: NODE_VERSION
            value: ~16.13.0
          - key: NODE_ENV
            value: production
          - key: CLOUDINARY_NAME
            sync: false
          - key: CLOUDINARY_KEY
            sync: false
          - key: CLOUDINARY_SECRET
            sync: false
          - key: DATABASE_URL
            fromDatabase:
              name: strapi
              property: connectionString
          - key: JWT_SECRET
            generateValue: true
          - key: ADMIN_JWT_SECRET
            generateValue: true
          - key: API_TOKEN_SALT
            generateValue: true
          - key: APP_KEYS
            generateValue: true

      - type: web
        name: productivity-tips-web
        env: node
        plan: free
        # Update the following line with your Remix GitHub repo
        repo: https://github.com/render-examples/strapiconf2022-workshop-remix
        branch: step-5
        buildCommand: npm install && npm run build
        startCommand: npm start
        envVars:
          - key: STRAPI_URL_BASE
            fromService:
              type: web
              name: productivity-tips-api
              envVarKey: RENDER_EXTERNAL_URL

    databases:
      - name: strapi
        plan: free # This database will expire 90 days after creation
Войдите в полноэкранный режим Выйдите из полноэкранного режима
  1. Сохраните изменения и отправьте их в удаленный репозиторий.
    $ git add render.yaml
    $ git commit
    $ git add
Войти в полноэкранный режим Выход из полноэкранного режима
  1. Войдите в панель управления Render.
  2. Нажмите на New и выберите Blueprint.
  3. Добавьте репозиторий, в котором приложение рендеринга сможет найти файл render.yaml. В данном случае это репозиторий strapiconf2022-workshop-strapi.

Когда вы выберете репо, вам будет предложено добавить следующие данные:

  • Service Group Name. Это уникальное имя, которое используется для идентификации проекта в вашей учетной записи.
  • CLOUDINARY_NAME.
  • CLOUDINARY_KEY
  • CLOUDINARY_SECRET.

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

  1. Войдите в приборную панель Cloudinary.
  2. Нажмите Начать конфигурирование.
  3. Скопируйте значения следующих полей:

    • cloud_name
    • api_key
  4. Вставьте скопированные значения в следующие поля соответственно:

    • CLOUDINARY_NAME.
    • CLOUDINARY_KEY
    • CLOUDINARY_SECRET.

После заполнения полей нажмите Apply
Затем дождитесь, пока Render развернет ваше приложение.

Заключение

После развертывания приложения вы можете увидеть адрес развернутого приложения, перейдя на приборную панель, затем выберите:

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

Чтобы запустить приложение, вам нужно сделать следующее:

  1. Перейдите в панель администратора приложения Strapi (по адресу /admin).
  2. Перейдите в раздел Настройки —> Токены API —> Создать новый токен API.
  3. Скопируйте созданный API-токен.
  4. Откройте службу productivity-tips-web.
  5. Перейдите в раздел Окружение и нажмите Добавить переменные окружения
  6. Установите ключ STRAPI_API_TOKEN и вставьте сгенерированный API-токен.
  7. Нажмите на Save Changes для развертывания приложения.

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

Чтобы расширить свои знания, обязательно ознакомьтесь со следующими ссылками:

  • Remix Docs
  • Приложение Jokes — учебник по Remix
  • Развертывание приложений Strapi на Render
  • Док-ты и интеграции хостинга Render

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