<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Self-Hosted</title><subtitle>Мой опыт использования Self-Hosted решений</subtitle><author><name>Self-Hosted</name></author><id>https://teletype.in/atom/self-hosted</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/self-hosted?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@self-hosted?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=self-hosted"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/self-hosted?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-21T06:10:42.138Z</updated><entry><id>self-hosted:e-commerce-store-with-nuxt3-and-strapi</id><link rel="alternate" type="text/html" href="https://teletype.in/@self-hosted/e-commerce-store-with-nuxt3-and-strapi?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=self-hosted"></link><title>Как создать интернет-магазин с Nuxt3 и Strapi</title><published>2023-03-19T14:29:51.808Z</published><updated>2023-04-07T08:32:38.133Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/a2/ee/a2ee8cf9-b1e2-45bb-8fa0-6b4adcc36251.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/e9/29/e9296576-ad2e-4076-8e9a-f9ccee9718b0.png&quot;&gt;В этом руководстве мы собираемся создать интернет-магазин с фронтендом на Nuxt и бэкендом на Strapi, используя официального руководство How to build an E-commerce Store with Nuxt.js and Strapi.</summary><content type="html">
  &lt;p id=&quot;jVU5&quot;&gt;В этом руководстве мы собираемся создать интернет-магазин с фронтендом на Nuxt и бэкендом на &lt;a href=&quot;https://strapi.io/&quot; target=&quot;_blank&quot;&gt;Strapi&lt;/a&gt;, используя официального руководство &lt;a href=&quot;https://strapi.io/blog/how-to-build-an-e-commerce-store-with-nuxt-js-and-strapi&quot; target=&quot;_blank&quot;&gt;How to build an E-commerce Store with Nuxt.js and Strapi.&lt;/a&gt;&lt;/p&gt;
  &lt;figure id=&quot;g2Il&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e9/29/e9296576-ad2e-4076-8e9a-f9ccee9718b0.png&quot; width=&quot;2000&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;rN5R&quot;&gt;Вам для урока понадобятся:&lt;/p&gt;
  &lt;ul id=&quot;jCNV&quot;&gt;
    &lt;li id=&quot;b9m4&quot;&gt;Обязательно пройденное руководство по &lt;a href=&quot;https://vuejs.org/tutorial/#step-1&quot; target=&quot;_blank&quot;&gt;vuejs&lt;/a&gt; или &lt;a href=&quot;https://www.vuemastery.com/courses/intro-to-vue-3/intro-to-vue3&quot; target=&quot;_blank&quot;&gt;курс&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;tAts&quot;&gt;Знакомство с &lt;a href=&quot;https://nodejs.org/en/docs/guides/getting-started-guide&quot; target=&quot;_blank&quot;&gt;nodejs&lt;/a&gt;(рекомендованная версия v18)&lt;/li&gt;
    &lt;li id=&quot;W9Zv&quot;&gt;Знакомство с фреймворком &lt;a href=&quot;https://getbootstrap.com/&quot; target=&quot;_blank&quot;&gt;Bootstrap&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;752u&quot;&gt;Установка Strapi&lt;/h2&gt;
  &lt;p id=&quot;tpOd&quot;&gt;Из документации основные преимущества Strapi - это гибкость, открытый исходный код, современный Headless подход к системе управления контентом, где вам не потребуется писать много кода при этом эффективно доставлять контент.  Перед локальной установкой вы можете попробовать &lt;a href=&quot;https://strapi.io/demo&quot; target=&quot;_blank&quot;&gt;демонстрационную версию&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;cV0s&quot;&gt;Для установки воспользуемся актуальными требованиями и рекомендация на github&amp;#x60;е , соответственно нам потребуется операционная система Ubuntu и рекомендуется установить nodejs v18.x.&lt;/p&gt;
  &lt;h3 id=&quot;yMh8&quot;&gt;Установка nodejs v18 с помощью nvm&lt;/h3&gt;
  &lt;p id=&quot;4al8&quot;&gt;Переходим на страничку с документацией по установке &lt;a href=&quot;https://github.com/nvm-sh/nvm#installing-and-updating&quot; target=&quot;_blank&quot;&gt;nvm&lt;/a&gt; и выполняем скрипт установки предварительно создав пользователя strapi &lt;/p&gt;
  &lt;pre id=&quot;EEfE&quot;&gt;useradd strapi
sudo su - strapi
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
nvm install 18&lt;/pre&gt;
  &lt;p id=&quot;WosO&quot;&gt;Проверяем установку:&lt;/p&gt;
  &lt;pre id=&quot;vrbQ&quot;&gt;node --version
v18.15.0&lt;/pre&gt;
  &lt;p id=&quot;bpok&quot;&gt;Обновляем npm:&lt;/p&gt;
  &lt;pre id=&quot;jQNJ&quot;&gt;npm install -g npm
npm --version
9.6.2&lt;/pre&gt;
  &lt;p id=&quot;asJO&quot;&gt;Устанавливаем yarn и проверяем:&lt;/p&gt;
  &lt;pre id=&quot;NUKS&quot;&gt;npm install -g yarn
yarn  --version
1.22.19&lt;/pre&gt;
  &lt;h3 id=&quot;bzag&quot;&gt;Создаем и запустим strapi приложение&lt;/h3&gt;
  &lt;p id=&quot;22HN&quot;&gt;Ранее мы установили nodejs из под пользователя strapi то и запускать необходимо из-под него.&lt;/p&gt;
  &lt;pre id=&quot;2MPY&quot;&gt;strapi@rock-5b:~$ pwd
/home/strapi
strapi@rock-5b:~$ yarn create strapi-app app --quickstart&lt;/pre&gt;
  &lt;p id=&quot;UPiP&quot;&gt;Видим что приложение запустилось:&lt;/p&gt;
  &lt;pre id=&quot;WxoC&quot;&gt;┌────────────────────┬──────────────────────────────────────────────────┐
│ Time               │ Sun Mar 19 2023 15:14:22 GMT+0500 (Yekaterinbur… │
│ Launched in        │ 6130 ms                                          │
│ Environment        │ development                                      │
│ Process PID        │ 47802                                            │
│ Version            │ 4.8.2 (node v18.15.0)                            │
│ Edition            │ Community                                        │
│ Database           │ sqlite                                           │
└────────────────────┴──────────────────────────────────────────────────┘
 Actions available
One more thing...
Create your first administrator 💻 by going to the administration panel at:
┌─────────────────────────────┐
│ http://localhost:1337/admin │
└─────────────────────────────┘&lt;/pre&gt;
  &lt;p id=&quot;GyRB&quot;&gt;Потребление памяти до 512Мб&lt;/p&gt;
  &lt;figure id=&quot;TdMl&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2c/87/2c876b6c-4090-4aba-850c-05ccf5af7177.png&quot; width=&quot;1030&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;AJgb&quot;&gt;Создаем пользователя&lt;/h3&gt;
  &lt;p id=&quot;B2hs&quot;&gt;Для создания пользователя открываем страничку &lt;a href=&quot;http://localhost:1337/admin&quot; target=&quot;_blank&quot;&gt;http://localhost:1337/admin&lt;/a&gt; с панелью администратора. Так как мы установили strapi на удаленном хосте, то при попытке подключится по адресу отличному от localhost&amp;#x60;а приводит к ошибке:&lt;/p&gt;
  &lt;figure id=&quot;V56y&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/9f/af/9faf0325-0251-43e1-a2f7-b9be80ff7e3e.png&quot; width=&quot;1282&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;SqEu&quot;&gt;Во избежании этого необходимо пробросить порт 1337 с удаленного сервера на localhost:&lt;/p&gt;
  &lt;pre id=&quot;c6rn&quot;&gt;ssh -L 1337:localhost:1337 root@192.168.1.10&lt;/pre&gt;
  &lt;figure id=&quot;0dmY&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/00/13/00130703-1624-41e0-b457-1ca8d0e42e25.png&quot; width=&quot;575&quot; /&gt;
    &lt;figcaption&gt;В пароле должно быть не менее 8 символов, 1 прописная, 1 строчная и 1 цифра.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;hSs4&quot;&gt;Получаем публичный адрес&lt;/h3&gt;
  &lt;p id=&quot;yqL4&quot;&gt;Для получения публичного адреса &lt;a href=&quot;https://api.my-domain.ru&quot; target=&quot;_blank&quot;&gt;https://api.my-domain.ru&lt;/a&gt;, добавляем параметр url в конфиг app/config/server.js:&lt;/p&gt;
  &lt;pre id=&quot;dXPg&quot;&gt;module.exports = ({ env }) =&amp;gt; ({
  host: env(&amp;#x27;HOST&amp;#x27;, &amp;#x27;0.0.0.0&amp;#x27;),
  port: env.int(&amp;#x27;PORT&amp;#x27;, 1337),
  url: &amp;#x27;https://api.my-domain.ru/&amp;#x27;,
  app: {
    keys: env.array(&amp;#x27;APP_KEYS&amp;#x27;),
  },
  webhooks: {
    populateRelations: env.bool(&amp;#x27;WEBHOOKS_POPULATE_RELATIONS&amp;#x27;, false),
  },
});&lt;/pre&gt;
  &lt;p id=&quot;8-hint&quot;&gt;И обязательно пересобираем админский интерфейс&lt;/p&gt;
  &lt;pre id=&quot;PvIs&quot;&gt;yarn strapi build&lt;/pre&gt;
  &lt;p id=&quot;MVeq&quot;&gt;И настраиваем systemd сервис /etc/systemd/system/strapi-app.service: &lt;/p&gt;
  &lt;pre id=&quot;84xk&quot;&gt;[Unit]
Description=Strapi app
After=network.target

[Service]
ExecStart=/home/strapi/.nvm/versions/node/v18.15.0/bin/node /home/strapi/app/node_modules/.bin/strapi start
User=strapi
WorkingDirectory=/home/strapi/app
Environment=NODE_ENV=production
Environment=PATH=/home/strapi/.nvm/versions/node/v18.15.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Restart=always

[Install]
WantedBy=multi-user.target&lt;/pre&gt;
  &lt;p id=&quot;WBi6&quot;&gt;Запускаем сервис strapi-app:&lt;/p&gt;
  &lt;pre id=&quot;FcG3&quot;&gt;systemctl daemon-reload
systemctl start strapi-app
systemctl status strapi-app
root@rock-5b:~# systemctl status strapi-app
● strapi-app.service - Strapi app
     Loaded: loaded (/etc/systemd/system/strapi-app.service; disabled; vendor preset: enabled)
     Active: active (running) since Mon 2023-03-20 00:08:33 +05; 10s ago
   Main PID: 53474 (node)
      Tasks: 11 (limit: 18504)
     Memory: 135.3M
        CPU: 4.565s
     CGroup: /system.slice/strapi-app.service
             └─53474 /home/strapi/.nvm/versions/node/v18.15.0/bin/node /home/strapi/app/node_modules/.bin/strapi start

Mar 20 00:08:36 rock-5b node[53474]: │ Version            │ 4.8.2 (node v18.15.0)                            │
Mar 20 00:08:36 rock-5b node[53474]: │ Edition            │ Community                                        │
Mar 20 00:08:36 rock-5b node[53474]: │ Database           │ sqlite                                           │
Mar 20 00:08:36 rock-5b node[53474]: └────────────────────┴──────────────────────────────────────────────────┘
Mar 20 00:08:36 rock-5b node[53474]:  Actions available
Mar 20 00:08:36 rock-5b node[53474]: Welcome back!
Mar 20 00:08:36 rock-5b node[53474]: To manage your project 🚀, go to the administration panel at:
Mar 20 00:08:36 rock-5b node[53474]: https://api.my-domain.ru/admin
Mar 20 00:08:36 rock-5b node[53474]: To access the server ⚡️, go to:
Mar 20 00:08:36 rock-5b node[53474]: https://api.my-domain.ru&lt;/pre&gt;
  &lt;h3 id=&quot;fZZF&quot;&gt;Устанавливаем HAProxy&lt;/h3&gt;
  &lt;p id=&quot;nkpm&quot;&gt;Прокси используется для получения безопасного TLS соединения и получения публичного url &lt;a href=&quot;https://api.my-domain.ru&quot; target=&quot;_blank&quot;&gt;https://api.my-domain.ru&lt;/a&gt;&lt;/p&gt;
  &lt;pre id=&quot;T4w5&quot;&gt;&lt;/pre&gt;
  &lt;p id=&quot;o3gI&quot;&gt; Устанавливаем пакет haproxy по &lt;a href=&quot;https://docs.strapi.io/dev-docs/deployment/haproxy-proxy&quot; target=&quot;_blank&quot;&gt;инструкции&lt;/a&gt;, но как альтернативу можно рассмотреть Nginx, Caddy, PM2:&lt;/p&gt;
  &lt;pre id=&quot;6Bt8&quot;&gt;apt-get install haproxy&lt;/pre&gt;
  &lt;p id=&quot;wVfq&quot;&gt;Настраиваем бэкенд для strapi добавим кониг ниже /etc/haproxy/haproxy.cfg&lt;/p&gt;
  &lt;pre id=&quot;04bn&quot;&gt;frontend api.example.com
        bind *:80
        default_backend strapi-backend

backend strapi-backend
        server local 127.0.0.1:1337&lt;/pre&gt;
  &lt;p id=&quot;fMYH&quot;&gt;И перезапустим haproxy:&lt;/p&gt;
  &lt;pre id=&quot;WI0k&quot;&gt;systemctl restart haproxy
systemctl status haproxy&lt;/pre&gt;
  &lt;h3 id=&quot;pvgm&quot;&gt;Настраиваем TLS &lt;/h3&gt;
  &lt;p id=&quot;7Kxi&quot;&gt;Устанавливаем пакет cerbot:&lt;/p&gt;
  &lt;pre id=&quot;oXue&quot;&gt;apt-get install certbot&lt;/pre&gt;
  &lt;p id=&quot;nwwG&quot;&gt;Настраиваем бэкенд для certbot в /etc/haproxy/haproxy.cfg:&lt;/p&gt;
  &lt;pre id=&quot;k1Il&quot;&gt;frontend api.my-domain.ru
        bind *:80
        #bind *:443 ssl crt /etc/letsencrypt/api.my-domain.ru.pem
        #http-request redirect scheme https unless { ssl_fc }

        acl letsencrypt-acl path_beg /.well-known/acme-challenge/
        use_backend letsencrypt-backend if letsencrypt-acl
        
backend letsencrypt-backend
        server certbot 127.0.0.1:8899&lt;/pre&gt;
  &lt;p id=&quot;n8vU&quot;&gt;Далее перезапускаем haproxy и запрашиваем сертификат:&lt;/p&gt;
  &lt;pre id=&quot;ecTQ&quot;&gt;systemctl restart haproxy
certbot certonly --standalone -d api.my-domain.ru --non-interactive --agree-tos --http-01-port=8899
cat /etc/letsencrypt/live/api.sh3h.ru/fullchain.pem /etc/letsencrypt/live/api.my-domain.ru/privkey.pem &amp;gt; /etc/letsencrypt/api.my-domain.ru.pem&lt;/pre&gt;
  &lt;p id=&quot;qtsw&quot;&gt;После раскомментируем строчки с настройкой SSL и перезапускаем haproxy снова&lt;/p&gt;
  &lt;p id=&quot;QeUL&quot;&gt;Открываем в браузере публичный url &lt;a href=&quot;https://api.my-domain.ru/admin&quot; target=&quot;_blank&quot;&gt;https://api.my-domain.ru/admin&lt;/a&gt; и убеждаемся, что haproxy создает безопасное соединение и панель администратора успешно открывается.&lt;/p&gt;
  &lt;figure id=&quot;0sPN&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ea/54/ea54baca-1054-4bc9-980e-76ea31442fb5.png&quot; width=&quot;1250&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;hSss&quot;&gt;Создаем бэкенд API&lt;/h2&gt;
  &lt;p id=&quot;spkO&quot;&gt;У нас уже запущен и работает Strapi; следующим шагом будет создание content-types нашего приложения.&lt;/p&gt;
  &lt;h3 id=&quot;fyM8&quot;&gt;&lt;strong&gt;1. Создаем collection type для товаров&lt;/strong&gt;&lt;/h3&gt;
  &lt;figure id=&quot;Dd62&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/4f/93/4f93c34c-daac-4e3e-87c7-0700c5217efd.png&quot; width=&quot;1598&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;01G9&quot;&gt;Создайте следующие поля в разделе product content-type&lt;/p&gt;
  &lt;ul id=&quot;xvPc&quot;&gt;
    &lt;li id=&quot;JWog&quot;&gt;Name - short text&lt;/li&gt;
    &lt;li id=&quot;bUq0&quot;&gt;Description - short text&lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;VMW4&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/18/72/18723eb6-b5b6-4308-887f-fcaeaf5ed76e.png&quot; width=&quot;1590&quot; /&gt;
  &lt;/figure&gt;
  &lt;ul id=&quot;en0G&quot;&gt;
    &lt;li id=&quot;EuPb&quot;&gt;Price - decimal number&lt;/li&gt;
    &lt;li id=&quot;PVcd&quot;&gt;Image - multiple media&lt;/li&gt;
    &lt;li id=&quot;tSRR&quot;&gt;Slug - UID&lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;4WUM&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a1/9b/a19b9437-365e-4d09-be2a-e501d236c38e.png&quot; width=&quot;1608&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;pijo&quot;&gt;&lt;strong&gt;2. Создаем collection type для категорий&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;fHNT&quot;&gt;Аналогично товарам создаем collection type для категорий товаров с полями:&lt;/p&gt;
  &lt;ul id=&quot;UcBI&quot;&gt;
    &lt;li id=&quot;Ak9m&quot;&gt;Name - short text&lt;/li&gt;
    &lt;li id=&quot;PssE&quot;&gt;Slug - UID&lt;/li&gt;
    &lt;li id=&quot;0wFu&quot;&gt;Связь - один ко многим товарам&lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;ddTm&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/b7/99/b7998ae2-2be6-4cf7-afc2-50dc269c17e6.png&quot; width=&quot;1614&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fmQ7&quot;&gt;&lt;strong&gt;2. Создаем collection type для заказов&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;7PZq&quot;&gt;Аналогично товарам создаем collection type для заказов с полями:&lt;/p&gt;
  &lt;ul id=&quot;4cjB&quot;&gt;
    &lt;li id=&quot;TIFD&quot;&gt;Item as JSON&lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;rey8&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7a/ec/7aeca68e-e44c-4f3f-a2b9-2d2efa9a6817.png&quot; width=&quot;1548&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;HPXG&quot;&gt;&lt;strong&gt;2. Создаем collection type для клиентов&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;J22W&quot;&gt;Аналогично товарам создаем collection type для заказов с полями:&lt;/p&gt;
  &lt;ul id=&quot;5xDh&quot;&gt;
    &lt;li id=&quot;JWtt&quot;&gt;Name - short text&lt;/li&gt;
    &lt;li id=&quot;RRFf&quot;&gt;Phone - big int&lt;/li&gt;
    &lt;li id=&quot;gbOO&quot;&gt;Email - email&lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;SsRF&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/24/0e/240ec9a0-595a-4ec2-a1a3-68ec0c84d1ef.png&quot; width=&quot;1564&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;qcDd&quot;&gt;Сохраняем все collection type и получаем токен к API Settings -&amp;gt; API Tokens-&amp;gt; Create new API Token&lt;/p&gt;
  &lt;figure id=&quot;v1m4&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/41/29/4129ca90-4f60-4a6f-b516-303c07d29762.png&quot; width=&quot;2030&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;zbj9&quot;&gt;Теперь можно проверить нам API в формате JSON:&lt;/p&gt;
  &lt;pre id=&quot;9yyW&quot;&gt;export STRAPI_TOKEN=токен полученный выше
curl -sH &amp;quot;Authorization: bearer $STRAPI_TOKEN&amp;quot; http://api.my-domain.ru/api/products | jq .
{
  &amp;quot;data&amp;quot;: [],
  &amp;quot;meta&amp;quot;: {
    &amp;quot;pagination&amp;quot;: {
      &amp;quot;page&amp;quot;: 1,
      &amp;quot;pageSize&amp;quot;: 25,
      &amp;quot;pageCount&amp;quot;: 0,
      &amp;quot;total&amp;quot;: 0
    }
  }
}&lt;/pre&gt;
  &lt;h2 id=&quot;aKdm&quot;&gt;Устанавливаем Nuxt&lt;/h2&gt;
  &lt;p id=&quot;SDis&quot;&gt;Мы будет использовать Nuxt в качестве фреймворка в режиме &lt;a href=&quot;https://nuxtjs.org/docs/concepts/server-side-rendering/&quot; target=&quot;_blank&quot;&gt;рендеринга на стороне сервера&lt;/a&gt; универсальных приложение Vue.js.&lt;/p&gt;
  &lt;p id=&quot;01H6&quot;&gt;Для установки необходимо выполнить команды из-под пользователя strapi:&lt;/p&gt;
  &lt;pre id=&quot;xCQA&quot;&gt;npx nuxi init front
cd front
yarn install&lt;/pre&gt;
  &lt;p id=&quot;EofW&quot;&gt;Запуск сервера разработки фронта:&lt;/p&gt;
  &lt;pre id=&quot;s0jr&quot;&gt;yarn dev -o&lt;/pre&gt;
  &lt;p id=&quot;VfvT&quot;&gt;Наш фронт запустится на порту 3000. Аналогично бэкенду создадим systemd сервис nuxt-front и настроим бэкенд для haproxy:&lt;/p&gt;
  &lt;p id=&quot;lATl&quot;&gt;Далее запускаем сервис и перезагружаем haproxy:&lt;/p&gt;
  &lt;pre id=&quot;sGhv&quot;&gt;systemctl start nuxt-front
systemctl restart haproxy&lt;/pre&gt;
  &lt;p id=&quot;XPLl&quot;&gt;И открываем в браузере наш фронтенд по адресу https://store.my-domain.ru/:&lt;/p&gt;
  &lt;figure id=&quot;bvzS&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/01/f7/01f70258-7ebc-4ef5-8cab-1572136d835b.png&quot; width=&quot;1860&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;cHTr&quot;&gt;Создаем  фронтенд интернет-магазина&lt;/h2&gt;
  &lt;p id=&quot;716j&quot;&gt;Перед тем как приступить к создания внешнего интерфейса нашего интернет-магазина необходимо:&lt;/p&gt;
  &lt;ol id=&quot;1SMh&quot;&gt;
    &lt;li id=&quot;N54E&quot;&gt;Подготовить среду для локальной разработки.&lt;/li&gt;
    &lt;ul id=&quot;nDOE&quot;&gt;
      &lt;li id=&quot;6XG2&quot;&gt;Установить IDE - это может быть WebStorm. PyCharm, VS Code + Vetur&lt;/li&gt;
      &lt;li id=&quot;oNUd&quot;&gt;Установить git, nodejs v18, yarn, nuxt&lt;/li&gt;
      &lt;li id=&quot;Qt8F&quot;&gt;Создать приватный репозиторий на &lt;a href=&quot;https://github.com/&quot; target=&quot;_blank&quot;&gt;https://github.com/&lt;/a&gt;&lt;/li&gt;
      &lt;li id=&quot;ikvM&quot;&gt;Настроить деплой на наш удаленный сервер store.my-domain.ru.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;1tUl&quot;&gt;Получить макет сайта для верстки в HTML или воспользоваться готовым шаблоном на bootstrap v4&lt;/li&gt;
    &lt;ul id=&quot;AWyw&quot;&gt;
      &lt;li id=&quot;2ab1&quot;&gt;Получить табличку с ассортиментом и ценами интернет-магазина&lt;/li&gt;
      &lt;li id=&quot;82wN&quot;&gt;Получить и обработать фотографии для каталога товаров. Картинки должны быть выровнены по высоте и ширине, иметь одинаковое разрешение по высоте и ширине в пикселя в соответствии с максимальным разрешением в макете сайта. Далее необходимо оптимизировать все картинки по размеру используя например инструмент &lt;a href=&quot;https://tinypng.com/&quot; target=&quot;_blank&quot;&gt;https://tinypng.com/&lt;/a&gt;.&lt;/li&gt;
      &lt;li id=&quot;xp67&quot;&gt;Загрузить все товары в ручную через strapi бэкенд сайта &lt;a href=&quot;https://api.my-domain.ru/&quot; target=&quot;_blank&quot;&gt;https://api.my-domain.ru/&lt;/a&gt;. Так же можно воспользоваться API для импорта большого объема данных из таблички или мигрировать данные из одного из конструкторов сайтов для e-commerce.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;V7On&quot;&gt;В итоге мы должны получить либо готовый шаблон сайта на Bootstrap или вам потребуется создать свой шаблон, что заслуживает написание отдельной статьи, где за основу можно взять статью &lt;a href=&quot;https://designmodo.com/create-bootstrap-theme/&quot; target=&quot;_blank&quot;&gt;How to Create a Custom Bootstrap Theme from Scratch. &lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;76X0&quot;&gt;Для демонстрации возможностей strapi и nuxt в данной статье, возьмем готовый бесплатный &lt;a href=&quot;https://echotemplate.com/product/olog-ecommerce-responsive-html-template&quot; target=&quot;_blank&quot;&gt;шаблон&lt;/a&gt; сайта для онлайн коммерции.&lt;/p&gt;
  &lt;figure id=&quot;Blku&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e2/ef/e2efaabf-2e76-47d2-9d7d-df3f837e29ac.png&quot; width=&quot;1900&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;V5lp&quot;&gt;Переносим статику Bootstrap в nuxt&lt;/h3&gt;
  &lt;p id=&quot;Oql6&quot;&gt;После того как распаковали архив с шаблоном сайта раскладываем полученные файлы в проект front:&lt;/p&gt;
  &lt;pre id=&quot;g54I&quot;&gt;olog-ecommerce-responsive-html-template-1.0/dist =&amp;gt; front/public/dist
olog-ecommerce-responsive-html-template-1.0/src/scss/vendors =&amp;gt; front/public/vendors
olog-ecommerce-responsive-html-template-1.0/src/scss =&amp;gt; front/assets/scss&lt;/pre&gt;
  &lt;p id=&quot;CJ31&quot;&gt;Подключаем scss стили в конфиге front/nuxt.config.ts:&lt;/p&gt;
  &lt;pre id=&quot;A4Y0&quot;&gt;export default defineNuxtConfig({
    css: [
        &amp;#x27;@/assets/scss/main.scss&amp;#x27;
    ],
}&lt;/pre&gt;
  &lt;p id=&quot;GwLt&quot;&gt;Воспользуемся IDE и откроем index.html файл шаблона, где свернем все основные секции HTML блоков&lt;/p&gt;
  &lt;figure id=&quot;lr2e&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ef/f4/eff436be-791f-40ef-a84e-a886b2b6028c.png&quot; width=&quot;1470&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;RUV0&quot;&gt;Перенесем полученные секции в vue компоненты во внутрь тэга &amp;lt;template&amp;gt;:&lt;/p&gt;
  &lt;pre id=&quot;1FvN&quot;&gt;Header Area =&amp;gt; front/components/HeaderArea.vue
Banner Area =&amp;gt; front/components/BannerArea.vue
Features Section =&amp;gt; front/components/FeaturesSection.vue
About Area =&amp;gt; front/components/AboutArea.vue
Populer Product =&amp;gt; front/components/PopulerProduct.vue
Categorys Section =&amp;gt; front/components/CategorysSection.vue
Features Section of customersreview =&amp;gt; front/components/FeaturesSectionCustomer.vue
Footer =&amp;gt; front/components/Footer.vue &lt;/pre&gt;
  &lt;figure id=&quot;KrJv&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/24/04/2404e13e-7b02-4e3d-9f4f-b1dfcfde0400.png&quot; width=&quot;1204&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;B48k&quot;&gt;Используя полученные компоненты &lt;em&gt;HeaderArea&lt;/em&gt; и &lt;em&gt;Footer&lt;/em&gt; создадим основной слой &lt;em&gt;front/layouts/default.vue&lt;/em&gt;:&lt;/p&gt;
  &lt;pre id=&quot;ei8L&quot;&gt;&amp;lt;template&amp;gt;
  &amp;lt;HeaderArea /&amp;gt;
  &amp;lt;main&amp;gt;
    &amp;lt;slot /&amp;gt;
  &amp;lt;/main&amp;gt;
  &amp;lt;Footer /&amp;gt;
&amp;lt;/template&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;uyXk&quot;&gt;Из оставшихся компонентов создадим главную страничку &lt;em&gt;front/pages/index.vue&lt;/em&gt;:&lt;/p&gt;
  &lt;pre id=&quot;GShV&quot;&gt;&amp;lt;template&amp;gt;
  &amp;lt;BannerArea /&amp;gt;
  &amp;lt;FeaturesSection /&amp;gt;
  &amp;lt;AboutArea /&amp;gt;
  &amp;lt;PopulerProduct /&amp;gt;
  &amp;lt;CategorysSection /&amp;gt;
  &amp;lt;FeaturesSectionCustomer /&amp;gt;
&amp;lt;/template&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;tIRG&quot;&gt;Далее подключим слои и странички приложении f&lt;em&gt;ront/&lt;/em&gt;app.vue:&lt;/p&gt;
  &lt;pre id=&quot;Mz4f&quot;&gt;&amp;lt;template&amp;gt;
  &amp;lt;NuxtLayout&amp;gt;
    &amp;lt;NuxtPage /&amp;gt;
  &amp;lt;/NuxtLayout&amp;gt;
&amp;lt;/template&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;1Icy&quot;&gt;Перед запуском приложение устанавливаем пакет sass:&lt;/p&gt;
  &lt;pre id=&quot;3E2I&quot;&gt; yarn add --dev sass sass
 yarn dev -o&lt;/pre&gt;
  &lt;h3 id=&quot;lSyV&quot;&gt;Подключаем бэкенд strapi&lt;/h3&gt;
  &lt;p id=&quot;mmZz&quot;&gt;Первым делом необходимо загрузить карточки продуктов и категорий в наш бэкенд&lt;/p&gt;
  &lt;figure id=&quot;A8Vg&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/23/b0/23b0cf59-f965-4aa2-a080-2648717991a8.png&quot; width=&quot;2540&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;57fo&quot;&gt;Используя &lt;a href=&quot;https://nuxt.com/docs/examples/composables/use-fetch&quot; target=&quot;_blank&quot;&gt;пример useFetch&lt;/a&gt; из документации nuxt, добавляем javascript код отвечающий за асинхронное получения данных из API strapi front/components/CategorysSection.vue:&lt;/p&gt;
  &lt;pre id=&quot;wQjp&quot;&gt;&amp;lt;script setup&amp;gt;
const config = useRuntimeConfig();
const { data: categories } = await useFetch(() =&amp;gt; &amp;#x60;/api/categories&amp;#x60;, {
  baseURL: config.API_URL,
  headers: {&amp;quot;Authorization&amp;quot;: &amp;quot;bearer &amp;quot; + config.API_TOKEN},
  query: { &amp;quot;populate&amp;quot;: &amp;quot;Image&amp;quot;},
})
&amp;lt;/script&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;ty03&quot;&gt;Так же используя пример &lt;a href=&quot;https://vuejs.org/guide/essentials/list.html#v-for&quot; target=&quot;_blank&quot;&gt;отрисовки списков&lt;/a&gt; и документации vue добавляем динамический рендеринг с помощью директивой &lt;em&gt;v-for &lt;/em&gt;из полученных данных карточки каталогов.&lt;/p&gt;
  &lt;figure id=&quot;JtJz&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/4e/3e/4e3ec8b7-953b-4c50-9751-804497510689.png&quot; width=&quot;2206&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;CvIC&quot;&gt;Аналогичным образом настроим динамический рендеринг компоненты PopulerProduct.vue с популярными продуктами.&lt;br /&gt;Далее настраиваем передачу параметров через конфиг &lt;em&gt;front/nuxt.config.ts&lt;/em&gt;:&lt;/p&gt;
  &lt;pre id=&quot;wD2B&quot;&gt;export default defineNuxtConfig({
    runtimeConfig: {
        API_TOKEN: &amp;quot;&amp;quot;,  // NUXT_API_TOKEN
        public: {
            API_URL: &amp;quot;&amp;quot;, // NUXT_PUBLIC_API_URL
        },
    },
    css: [
        &amp;#x27;@/assets/scss/main.scss&amp;#x27;
    ],
})&lt;/pre&gt;
  &lt;p id=&quot;FU5Q&quot;&gt;И настраиваем эти параметры в IDE конфигурации запуска фронта&lt;/p&gt;
  &lt;figure id=&quot;cgDZ&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7f/cd/7fcdae1f-8d5a-450e-87b2-021c5fb3b4d7.png&quot; width=&quot;1676&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;OTKL&quot;&gt;Запустим фронт и проверяем рендеринг главной странички&lt;/p&gt;
  &lt;figure id=&quot;yCR8&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d8/af/d8af8ac4-f946-420a-8c2b-97a02731b5bd.png&quot; width=&quot;1464&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fjsA&quot;&gt;Теперь добавим динамическую страничку с детализацией продукта &lt;em&gt;front/pages/product/[...slug].vue&lt;/em&gt;:&lt;/p&gt;
  &lt;pre id=&quot;mHZq&quot;&gt;&amp;lt;template&amp;gt;
  &amp;lt;BreadCrumb /&amp;gt;
  &amp;lt;ProductDetails /&amp;gt;
  &amp;lt;FeaturesSection /&amp;gt;
&amp;lt;/template&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;K42e&quot;&gt;И в компоненту ProductDetails добавим асинхронное получение данных о продукте по его slug из параметров маршрутизации запросов &lt;em&gt;front/components/ProductDetails.vue&lt;/em&gt;:&lt;/p&gt;
  &lt;pre id=&quot;gGUk&quot;&gt;&amp;lt;script setup&amp;gt;
const slug = useRoute().params.slug
const config = useRuntimeConfig()
const { data: products } = await useFetch(() =&amp;gt; &amp;#x60;/api/products&amp;#x60;, {
  baseURL: config.API_URL,
  headers: { &amp;quot;Authorization&amp;quot;: &amp;quot;bearer &amp;quot; + config.API_TOKEN },
  query: { &amp;quot;filters\[Slug\][$eq]&amp;quot;: slug, &amp;quot;populate&amp;quot;: &amp;quot;Image&amp;quot; }
})
&amp;lt;/script&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;tw5g&quot;&gt;И аналогично помощью директивой &lt;em&gt;v-for &lt;/em&gt;отрисуем список и передадим парамеры продукта в шаблонизатор&lt;/p&gt;
  &lt;figure id=&quot;HPkL&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c6/bd/c6bd32c9-fb9d-42e9-8821-0538fa450d5c.png&quot; width=&quot;1446&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Q4sU&quot;&gt;Так как в компоненте FeaturesSection используется автозапуск карусели на клиенте, то для корректного отображения установим и настроим карусель в nuxt&lt;/p&gt;
  &lt;pre id=&quot;Yf2h&quot;&gt;yarn install vue3-carousel&lt;/pre&gt;
  &lt;p id=&quot;3fkq&quot;&gt;Подключим Carousel в компоненте front/components/FeaturesSection.vue &lt;/p&gt;
  &lt;pre id=&quot;d2Qt&quot;&gt;&amp;lt;script&amp;gt;
import { Carousel, Navigation, Slide } from &amp;#x27;vue3-carousel&amp;#x27;
import &amp;#x27;vue3-carousel/dist/carousel.css&amp;#x27;

export default defineComponent({
  name: &amp;#x27;WrapAround&amp;#x27;,
  components: {
    Carousel,
    Slide,
    Navigation,
  },
})
&amp;lt;/script&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;3igJ&quot;&gt;И используем их в шаблоне &lt;/p&gt;
  &lt;figure id=&quot;bir2&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/74/75/747569b8-9586-43d4-b5ac-7e986a05c2fc.png&quot; width=&quot;1634&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;HtH6&quot;&gt;Проверяем рендеринг странички с продуктом&lt;/p&gt;
  &lt;figure id=&quot;2iWt&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ef/f5/eff598ff-b438-4052-923c-156e012bd1d4.png&quot; width=&quot;2304&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;U8Ut&quot;&gt;Создаем корзину покупок&lt;/h3&gt;
  &lt;p id=&quot;nyvd&quot;&gt;При нажатии на кнопку добавить в корзину будем добавлять ID - идентификатор продукта в список хранимый в локальном хранилище и на сервера.&lt;/p&gt;
  &lt;p id=&quot;kXJP&quot;&gt;Использует &lt;a href=&quot;https://pinia.vuejs.org/ssr/nuxt.html&quot; target=&quot;_blank&quot;&gt;документацию&lt;/a&gt; по библиотеке Pinia настроим хранилище для списка покупок &lt;em&gt;front/store/cart.ts&lt;/em&gt;:&lt;/p&gt;
  &lt;pre id=&quot;gDHP&quot;&gt;import { defineStore, skipHydrate } from &amp;#x27;pinia&amp;#x27;
import { useLocalStorage } from &amp;#x27;@vueuse/core&amp;#x27;
export const useCartStore = defineStore(&amp;#x27;cartStore&amp;#x27;, () =&amp;gt; {
    const cartItems = useLocalStorage(&amp;#x27;pinia/cart&amp;#x27;, [])
    function addValueToCartList(id: never) {
        cartItems.value.push(id)
    }
    return { addValueToCartList, cartItems: skipHydrate(cartItems)}
})&lt;/pre&gt;
  &lt;p id=&quot;Z8zo&quot;&gt;Далее в компоненту PopulerProduct импортируем функцию addValueToCartList - добавления товаров в корзину покупок в компоненту &lt;em&gt;front/components/PopulerProduct.vue&lt;/em&gt;:&lt;/p&gt;
  &lt;pre id=&quot;D72a&quot;&gt;&amp;lt;script setup&amp;gt;
import { useCartStore } from &amp;#x27;~/store/cart&amp;#x27;
const cartStore = useCartStore()
const { addValueToCartList } = cartStore
...&lt;/pre&gt;
  &lt;p id=&quot;940K&quot;&gt;И в шаблоне вызовем ее при клике на иконку с корзиной с помощь директивы &lt;em&gt;@click.prevent=&amp;quot;addValueToCartList(item.id)&amp;quot;&lt;/em&gt;&lt;/p&gt;
  &lt;figure id=&quot;EChh&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bb/c9/bbc95041-357d-41be-b7e6-6627c3424730.png&quot; width=&quot;1598&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;xlRS&quot;&gt;Аналогично подключим функцию addValueToCartList в компонент с детализацией продукта &lt;em&gt;front/components/ProductDetails.vue:&lt;/em&gt;&lt;/p&gt;
  &lt;pre id=&quot;NqRE&quot;&gt;&amp;lt;a class=&amp;quot;btn cart-bg &amp;quot; href=&amp;quot;#&amp;quot; @click.prevent=&amp;quot;addValueToCartList(item.id)&amp;quot;&amp;gt;Add to cart&lt;/pre&gt;
  &lt;p id=&quot;oOLL&quot;&gt;Так же импортируем переменную cartItems для отображения числа товаров в компоненте &lt;em&gt;front/components/HeaderArea.vue:&lt;/em&gt;&lt;/p&gt;
  &lt;pre id=&quot;BDce&quot;&gt;&amp;lt;script setup&amp;gt;
import { storeToRefs } from &amp;#x27;pinia&amp;#x27;
import { useCartStore } from &amp;#x27;~/store/cart.ts&amp;#x27;
const cartStore = useCartStore()
const { cartItems } = storeToRefs(cartStore)
&amp;lt;/script&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;HQOW&quot;&gt;И в шаблон подставим переменную с функцией длинны списка:&lt;/p&gt;
  &lt;pre id=&quot;AsHZ&quot;&gt;&amp;lt;span class=&amp;quot;cart&amp;quot;&amp;gt;{{ cartItems.length }}&amp;lt;/span&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;5SqD&quot;&gt;Проверяем работу кнопок, отображения счетчика товаров и убеждаемся, что после обновления странички счетчик товаров не изменился.&lt;/p&gt;
  &lt;figure id=&quot;0Yd4&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0e/52/0e52e2dc-67fb-47f6-bd02-c51abdfda513.png&quot; width=&quot;2312&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;znyi&quot;&gt;Теперь добавляем на страничку с покупками, где этот список будет использоваться для асинхронного получения данных о товарах в корзине. front/pages/cart.vue:&lt;/p&gt;
  &lt;pre id=&quot;4YOk&quot;&gt;&amp;lt;template&amp;gt;
  &amp;lt;BreadCrumb /&amp;gt;
  &amp;lt;Cart /&amp;gt;
&amp;lt;/template&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;nQl5&quot;&gt;Создаем компонуемый файл с товарами в корзине front/composables/useProducts.ts:&lt;/p&gt;
  &lt;pre id=&quot;YlbS&quot;&gt;import { stringify } from &amp;#x27;qs&amp;#x27;;
export default async function (items: number[]) {
    const config = useRuntimeConfig();
    const {data: products} = await useFetch(() =&amp;gt; &amp;#x60;/api/products?&amp;#x60; + stringify({
        filters: { id: { $in: items.length &amp;gt; 0 ? items : [-1]}},
        populate: &amp;quot;Image&amp;quot;,
    }, { encodeValuesOnly: true}), {
        baseURL: config.API_URL,
        headers: {&amp;quot;Authorization&amp;quot;: &amp;quot;bearer &amp;quot; + config.API_TOKEN},
    })
    return products.value
}&lt;/pre&gt;
  &lt;p id=&quot;elU1&quot;&gt;Так как список покупок хранится в локальном хранилище браузера нам потребуется серверный API для сохранения и получения списка покупок на стороне сервера. Создадим сильно упрощенный вариант без привязки к уникальной сессии пользователя.&lt;br /&gt;front/server/api/cart.get.ts:&lt;/p&gt;
  &lt;pre id=&quot;tfMS&quot;&gt;export default defineEventHandler(async (event) =&amp;gt; {
    const body = await readBody(event)
    await useStorage().setItem(&amp;#x27;db:cart&amp;#x27;, body)
    return true
})&lt;/pre&gt;
  &lt;p id=&quot;CcjH&quot;&gt;front/server/api/cart.post.ts:&lt;/p&gt;
  &lt;pre id=&quot;nv2u&quot;&gt;export default defineEventHandler(async (event) =&amp;gt; {
    const body = await readBody(event)
    await useStorage().setItem(&amp;#x27;db:cart&amp;#x27;, body)
    return true
})&lt;/pre&gt;
  &lt;p id=&quot;Utcy&quot;&gt;Соответственно добавляем эти функции, функции удаления и количества в хранилище для списка покупок &lt;em&gt;front/store/cart.ts&lt;/em&gt;:&lt;/p&gt;
  &lt;pre id=&quot;GuG7&quot;&gt;...
    function removeCartItem(id: never) {
        cartItems.value = cartItems.value.filter((number, i) =&amp;gt; number != id)
        pushCart()
    }
    function quantity(id: never) {
        return cartItems.value.filter((number, i) =&amp;gt; number == id).length
    }
    async function pushCart() {
        console.log(&amp;quot;pull cart&amp;quot;, cartItems.value)
        await useFetch(&amp;#x27;/api/cart&amp;#x27;, {
            method: &amp;#x27;post&amp;#x27;,
            body: { items: cartItems.value }
        })
    }
    async function fetchCart() {
        const { data: resData } = await useFetch(&amp;#x27;/api/cart&amp;#x27;)
        if (!resData.value) {
            return
        }
        cartItems.value.length = 0
        cartItems.value.push(...resData.value.items)
    }
    return { removeCartItem, addValueToCartList, pushCart, fetchCart, quantity, cartItems: skipHydrate(cartItems)}
})&lt;/pre&gt;
  &lt;p id=&quot;oj8v&quot;&gt;Далее создаем компонент с корзиной покупок &lt;em&gt;front/components/Cart.vue&lt;/em&gt;:&lt;/p&gt;
  &lt;pre id=&quot;snkv&quot;&gt;&amp;lt;script setup&amp;gt;
import { storeToRefs } from &amp;#x27;pinia&amp;#x27;
import { useCartStore } from &amp;#x27;~/store/cart.ts&amp;#x27;
const cartStore = useCartStore()
const { cartItems } = storeToRefs(cartStore)
const { fetchCart } = cartStore
const { quantity, removeCartItem } = cartStore
const config = useRuntimeConfig()
if (process.server) { await fetchCart()}
const products = await useProducts(cartItems.value.concat())
function removeProductItem(idx) {
  const id = products.data[idx].id
  products.data.splice(idx, 1)
  removeCartItem(id)
}
&amp;lt;/script&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;qeyX&quot;&gt;И полученные данные отрисовываем с помощью директивы v-for и при клике на Delete вызываем функцию removeProductItem, которая реактивно перерисовывает список товаров, удаляет все товары с данным id из хранилища покупок с сохранением его на сервере. &lt;/p&gt;
  &lt;figure id=&quot;B3KT&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ee/fe/eefe7eaa-033e-4b23-bc35-ce0e49ae2350.png&quot; width=&quot;1874&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;NFDb&quot;&gt;Еще необходимо вызывать функцию pushCart отправки списка покупок на сервер перед переходом на страничку корзины из компоненты &lt;em&gt;front/components/HeaderArea.vue&lt;/em&gt;:&lt;/p&gt;
  &lt;pre id=&quot;Kgsk&quot;&gt;...
const { pushCart } = cartStore
&amp;lt;/script&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;MVin&quot;&gt;и в шаблоне вызовем при клике&lt;/p&gt;
  &lt;pre id=&quot;7NJH&quot;&gt;&amp;lt;a href=&amp;quot;/cart&amp;quot; @click=&amp;quot;pushCart&amp;quot;&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;VMqN&quot;&gt; Проверим рендеринг странички с корзиной и кнопки удаления&lt;/p&gt;
  &lt;figure id=&quot;m1CR&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/9b/ef/9befc58e-59e1-4b51-be0c-f57a27cb029c.png&quot; width=&quot;2046&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;l3LN&quot;&gt;Так как для демонстрации мы взяли случайны шаблон магазина, а в вашем случае он может сильно отличатся по UX, то доводить это шаблон до полной функциональности смысла не вижу.&lt;/p&gt;
  &lt;p id=&quot;0f8p&quot;&gt;Все примеры кода можно найти в моем репозитории &lt;a href=&quot;https://github.com/kmlebedev/e-commerce-store-with-nuxt3-and-strapi&quot; target=&quot;_blank&quot;&gt;https://github.com/kmlebedev/e-commerce-store-with-nuxt3-and-strapi&lt;/a&gt;&lt;/p&gt;

</content></entry></feed>