Как объединить фронтенд и бэкенд в одном веб-приложении?

Как объединить фронтенд и бэкенд в одном приложении? Деплой pet-проекта

В инструкции рассказываем, как написать Docker-файл и настроить конфигурацию nginx, чтобы задеплоить веб-приложение на облачный сервер.

Введение

Привет! Меня зовут Артём Шумейко, я Python-разработчик и создатель одноименного канала на YouTube. Представьте, у вас есть проект с фронтенд- и бэкенд-частью. Первая работает на одном порту и отображает данные, после — передает бэкенду. Вторая функционирует на другом порту, принимает и обрабатывает эти данные, после чего возвращает ответ. Обычно сайты находятся на едином домене с фронтендом и бэкендом, а здесь — на двух отдельных. 

Будучи новичком я не понимал, как объединить фронтенд и бэкенд. Думал, нужно подключать два домена и неведомым образом их «подружить». Но все оказалось намного проще. В тексте расскажу о двух способах развертывания приложения и поделюсь подробной инструкцией. 

О проекте

В инструкции не будем писать веб-приложение с нуля — возьмем готовый проект, который состоит из 30-40 строк кода. Фронтенд будет отображать данные с бэкенда: плашку, items, картинки и данные с GitHub, Docker, nginx.


    @app.get("/items")
def get_items():
    items = [
        {
            "id": 1,
            "name": "Docker",
            "img": "https://static-00.iconduck.com/assets.00/docker-icon-2048x2048-5mc7mvtn.png",
        },
        {
            "id": 2,
            "name": "Nginx",
            "img": "https://www.svgrepo.com/show/373924/nginx.svg",
        },
        {
            "id": 3,
            "name": "GitHub",
            "img": "https://cdn-icons-png.flaticon.com/512/25/25231.png",
        },
    ]
    random.shuffle(items)
    return items
Фронтенд и бэкенд проекта.
Фронтенд и бэкенд проекта.

Весь проект локально запущен в двух терминалах. В первом — бэкенд с портом 8000, во втором — фронтенд с портом 5173. По сути, это два разных сервера, которые нужно соединить.

Схема работы проекта.
Вариант объединения фронтенда и бэкенда.

Чтобы объединить фронтенд и бэкенд, есть две стратегии. Первый способ — это реверс-прокси (обратный прокси), при котором мы не создаем домен. В нем разворачиваем фронтенд на основной сайт, а бэкенд — на адрес/API. Второй — это использование обратного прокси через создание поддомена. В нем создаем еще один домен к основному, например api.mysite.ru, чтобы разделять сайты. В тексте будем использовать обратный прокси без поддомена.

Два метода объединения фронтенда и бэкенда.
Два метода объединения фронтенда и бэкенда.

Настройка Docker-файла

Бэкенд

Нужно написать два простых Docker-файла, чтобы перенести данные в контейнеры. В образ Python добавляем slim, чтобы не занимать много места:


    FROM python:3.11.9-slim

Перемещаем все файлы в локальную директорию и добавляем зависимости в requirements.txt. После копируем файлы в Docker-образ:


    COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

Сначала устанавливаем файлы с зависимостями, которые редко меняются, и только потом остальные. Запускаем код: 


    CMD [ "python", "main.py" ]

Фронтенд

Копируем весь Docker-файл и добавляем его в папку frontend. В нем будем использовать не Python, а JavaScript и Node.js. 

Указываем последнюю версию ноды alpine и называем ее build:


    FROM node: alpine as build

Здесь зависимости находятся не в папке requirements.txt, а в package.json. Копируем их и добавляем в файл:


    COPY package.json package.json
RUN npm install

После — копируем оставшиеся файлы:


    COPY . .
RUN npm run build

Фронтенд поддерживает только статические файлы: JavaScript, HTML и CSS. Чтобы забрать их из контейнера, запускаем nginx прямо в коде:


    FROM nginx: stable-alpine

После команды npm build внутри Docker-образа появилась папка /dist. В ней хранятся созданные билды, а именно — статические файлы. Отправляем их в nginx:


    COPY --from=build /dist /usr/share/nginx/html 

Далее копируем nginx.conf в классическую директору nginx:


    COPY --from=build nginx.conf /etc/nginx/conf.d/default.conf

Указываем порт, на котором будет работать nginx, через EXPOSE 3000, но можно выбрать другой:


    EXPOSE 3000
CMD [ "nginx", "-g", "daemon off;" ]

Чтобы отдавать файлы index.html, создаем nginx.conf:


    server {
  listen 3000;

  location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    try_files $uri $uri/ /index.html =404;
  }

  include /etc/nginx/extra-conf.d/*.conf;
}

Общая сборка приложения

Создаем файл docker-compose, чтобы управлять сразу несколькими контейнерами. Здесь используем контейнер бэкенда и фронтенда в YAML-формате. 


    services:
  backend:
    build:
      context: ./backend
  frontend:
    build:
      context: ./frontend

Перед контейнерами запускаем nginx из готового образа DockerHub:


    services:
  nginx:
    image: nginx:stable-alpine
    ports:
      - "80:80"

  backend:
    build:
      context: ./backend
  frontend:
    build:
      context: ./frontend

Указываем стандартный порт HTTP — 80:80. 

Далее нужно выполнить две задачи: 

  • настроить приложение так, чтобы nginx запускался только после бэкенда и фронтенда;
  • добавить volumes, чтобы nginx видел файлы с локального сервера.

    services:
  nginx:
    image: nginx:stable-alpine
    ports:
      - "80:80"
    volumes:
      - './nginx.conf:/etc/nginx/nginx.conf'
    depends_on:
      - backend
      - frontend

Соединяем все контейнеры в единую сеть и указываем зависимости:


    services:
  nginx:
    image: nginx:stable-alpine
    ports:
      - "80:80"
    volumes:
      - './nginx.conf:/etc/nginx/nginx.conf'
    depends_on:
      - backend
      - frontend
    networks:
      - dev
    

  backend:
    build:
      context: ./backend
    networks:
      - dev

  frontend:
    build:
      context: ./frontend
    networks:
      - dev

Настройка конфигурации nginx

Используем готовый файл nginx.conf. Называем пользователя root, добавляем необходимое количество worker-нод и порт 80.


    user  root;
worker_processes  1;

events {
}

http {
    server {
        listen       80;
        server_name  localhost
    }
}

Указываем Location, чтобы при запуске выводить фронтенд на главную страницу. После — добавляем proxy_pass, чтобы проксировать запросы через nginx, и порт 3000, как на нашем сервере.


            location / {
            proxy_pass http://frontend:3000/;
        }

Далее подключаем бэкенд и добавляем в Location /api/. Порт — 8000, поскольку контейнер с бэкендом использует именно его.


            location /api/ {
            proxy_pass http://backend:8000/;
        }

Конфигурация nginx готова. 

Создание облачного сервера

Переходим в панель управления Selectel и создаем новый сервер. Указываем имя, регион и операционную систему — в нашем случае Ubuntu 24.04 LTS 64-bit. Для работы сайта подойдет минимальная конфигурация: 

  • 1 CPU,
  • 1 ГБ RAM,
  • 10 ГБ SSD,  
  • публичный IP-адрес.

Далее добавляем SSH-ключ. Указываем имя и копируем команду для терминала. После получаем публичный ключ и добавляем его в окно создания. 

Окно с созданием SSH-ключа.
Окно с созданием SSH-ключа.

Теперь можно автоматически подключаться к серверу без использования пароля, с помощью команды ssh root@<IP-адрес сервера>. Нажимаем Создать сервер.

Создание репозитория 

Создаем новый репозиторий для хранения кодовой базы. Если проект не конфиденциален и вы хотите клонировать его на сервер без дополнительной авторизации, сделайте репозиторий публичным. Далее — перейдите в терминал (предварительно не забудьте подключиться к серверу) и клонируйте репозиторий с помощью команды git clone <адрес репозитория>.

Создание репозитория.
Создание репозитория.

Поскольку у нас около 20 файлов, создаем файл .gitignore. Туда добавляем файлы с зависимостями — venv и node-modules. Пишем initial commit, чтобы отправить их в Git. 


    venv/
node_modules/

Переходим в терминал и обновляем пакеты, чтобы установить Git. Создаем клон и скачиваем код по HTTPS с GitHub. На выходе получаем папку test-deploy-site, в которой находятся все файлы.  

Код в терминале.
Код в терминале.

Деплой веб-приложения на сервер

Теперь нужно установить Docker. Используем готовый код — копируем и добавляем его в терминал.

Код в Docker.
Код в Docker.

Собираем все образы и добавляем их в контейнеры Docker:


    docker compose up --build

Адрес бэкенда и фронтенда скрыты от конечного пользователя и мошенников. Напрямую с ними работать не получится, только через nginx. 

Заключение

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

Автор: Артём Шумейко, создатель YouTube-канала.