«Под капотом»: как объединить фронтенд с бэкендом и не сломать веб-приложение

5212e0c6925a6369323588a4074e575e.png

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

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

О проекте

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

from fastapi import FastAPI
import random

@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

COPY . .

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

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. 

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

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

  • добавить volumes, чтобы nginx видел файлы с локального сервера.

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

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

networks:
  dev:

services:
  nginx:
    image: nginx:stable-alpine
    ports:
      - "80:80"
      - "443:443"
    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;
    }
}

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

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

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

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

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

user  root;
worker_processes  1;

events {
}

http {
    server {
        listen       80;

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

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

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

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

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

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

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

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

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

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

© Habrahabr.ru