Клиент-серверная архитектура

Клиент-серверная архитектура — модель распределённого приложения, где клиент инициирует запросы, а сервер обрабатывает их и возвращает ответы.

Зачем нужно

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

Где используется

  • Веб-приложения (браузер = клиент, Node.js/Python/Java = сервер)
  • Мобильные приложения (iOS/Android = клиент, REST API = сервер)
  • Настольные приложения с удалённым бэкендом
  • Любое сетевое взаимодействие: email (IMAP/SMTP), FTP, игры

Модель запрос-ответ

Клиент (браузер)              Сервер (Node.js)
        |                            |
        |------ HTTP Request ------->|
        |   GET /api/users           |
        |   Host: example.com        |
        |   Authorization: Bearer... |
        |                            |  → БД запрос
        |                            |  ← Данные из БД
        |<----- HTTP Response -------|
        |   200 OK                   |
        |   Content-Type: json       |
        |   [{id:1, name:...}]       |

Разделение ответственности

Клиент отвечает за:

// UI — отображение данных
function renderUsers(users) {
  const list = document.getElementById('user-list');
  list.innerHTML = users.map(u => `<li>${u.name}</li>`).join('');
}

// UX — интерактивность, валидация формы
document.getElementById('email').addEventListener('input', e => {
  const valid = /^[^@]+@[^@]+$/.test(e.target.value);
  e.target.setCustomValidity(valid ? '' : 'Некорректный email');
});

// Состояние — что показывать пользователю (loading/error/data)

Сервер отвечает за:

// Бизнес-логика и валидация (обязательно на сервере)
app.post('/api/users', async (req, res) => {
  const { email, role } = req.body;

  // Безопасность — никогда не доверяем клиенту
  if (!isValidEmail(email)) {
    return res.status(422).json({ error: 'Invalid email' });
  }

  // Авторизация — только сервер решает, что разрешено
  if (role === 'admin' && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  // Работа с БД — клиент не имеет прямого доступа
  const user = await db.users.create({ email, role: 'viewer' });
  res.status(201).json(user);
});

Варианты архитектуры

Монолит:
  Browser ──────────────► Node.js + Express (HTML + API + БД)

SPA + API:
  Browser ──────────────► Nginx (static SPA)
         └─────────────► Node.js API ──► PostgreSQL

Микросервисы:
  Browser ──────────────► API Gateway
                              ├──► User Service ──► User DB
                              ├──► Order Service ──► Order DB
                              └──► Notification Service ──► Redis

SSR (Next.js):
  Browser ◄── HTML ◄────► Next.js Server (рендер + API)
                                └──► Backend API / БД

Принцип разделения

Клиент (не доверяем):      Сервер (доверяем):
- Никогда не хранить       - Хранить секреты
  секреты (API keys,       - Проверять права доступа
  DB passwords)            - Валидировать данные
- Клиентская валидация     - Применять бизнес-правила
  — только UX              - Работать с БД напрямую
- Кэшировать UI-данные     - Генерировать токены

Частые ошибки

  • Хранение секретов (API ключей, паролей) в клиентском коде — они видны всем
  • Доверять клиентской валидации — злоумышленник может отправить любые данные
  • Реализация бизнес-логики только на клиенте — легко обойти
  • Прямой доступ браузера к БД без API-слоя — критическая уязвимость

Связанные темы

Ресурсы