Как реализовать загрузку и хранение файлов в S3-совместимых хранилищах

8 минут чтения
Средний рейтинг статьи — 4.7

Загрузка изображений и файлов — одна из базовых задач любого веб-приложения. Вместо того чтобы хранить файлы прямо на сервере, современные проекты используют облачные S3-совместимые хранилища — быстрые, масштабируемые и дешёвые решения для долговременного хранения данных.
Разберёмся, как это работает и как реализовать базовую загрузку изображений на примере Node.js + Express.

Что такое S3 и почему он стал стандартом

S3 (Simple Storage Service) — это сервис Amazon для хранения объектов (файлов) в «бакетах» (buckets).
Главная идея: вместо классической файловой системы сервер обращается к REST API хранилища, где каждый объект имеет уникальный ключ.

Но S3 стал не просто сервисом, а де-факто стандартом протокола.
Его API поддерживают десятки совместимых решений:

  • Wasabi,
  • Backblaze B2,
  • DigitalOcean Spaces,
  • Yandex Object Storage,
  • MinIO (для локального развертывания).

Это значит, что если твой код умеет работать с Amazon S3, он будет работать и с этими платформами.

Как это устроено

  1. Приложение (backend) принимает файл от клиента.
  2. Оно либо:
    • загружает файл само в хранилище через SDK,
    • либо выдаёт клиенту pre-signed URL, по которому клиент может сам отправить файл напрямую в бакет.
  3. Хранилище возвращает ссылку на объект — обычно это публичный URL или путь, доступный через CDN.

Пример: загрузка изображения через Express и AWS SDK v3

Начнём с классической серверной загрузки:

npm install express multer @aws-sdk/client-s3

Создадим upload.js:

import express from "express";
import multer from "multer";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import fs from "fs";
 
const app = express();
const upload = multer({ dest: "uploads/" });
 
// Настройки клиента
const s3 = new S3Client({
  region: "us-east-1",
  endpoint: "https://s3.wasabisys.com", // можно заменить на MinIO или Yandex
  credentials: {
    accessKeyId: process.env.S3_KEY,
    secretAccessKey: process.env.S3_SECRET,
  },
});
 
app.post("/upload", upload.single("image"), async (req, res) => {
  const file = req.file;
  try {
    const command = new PutObjectCommand({
      Bucket: "my-bucket",
      Key: file.originalname,
      Body: fs.createReadStream(file.path),
      ContentType: file.mimetype,
    });
 
    await s3.send(command);
    fs.unlinkSync(file.path); // чистим локальный tmp
 
    res.json({ message: "Файл загружен успешно" });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "Ошибка загрузки файла" });
  }
});
 
app.listen(3000, () => console.log("Server started on port 3000"));

Теперь можно отправить POST-запрос с файлом:

curl -F "image=@photo.jpg" http://localhost:3000/upload

Прямая загрузка с клиента (pre-signed URLs)

Чтобы разгрузить сервер, можно дать пользователю временную ссылку на прямую загрузку в бакет.

Бэкенд генерирует её так:

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
 
const client = new S3Client({ region: "us-east-1" });
 
const url = await getSignedUrl(
  client,
  new PutObjectCommand({ Bucket: "my-bucket", Key: "test.jpg" }),
  { expiresIn: 60 } // срок действия ссылки
);
 
console.log(url);

После этого фронтенд может выполнить обычный fetch(url, { method: "PUT", body: file }) — сервер в этом процессе не участвует.

Организация структуры хранения

Хорошая практика — структурировать ключи:

/uploads/2025/10/avatar_123.jpg
/backups/db_2025-10-07.tar.gz

Так проще организовать доступ и чистку старых данных.
Большинство хранилищ поддерживает жизненные циклы (lifecycle policies) — автоматическое удаление или архивирование старых объектов.

Управление доступом

  • Private bucket — по умолчанию, всё закрыто.
  • Public access можно выдать только для отдельных директорий (например, /public/images).
  • Для безопасной раздачи через CDN используют подписанные ссылки (signed URLs) или CloudFront.

Альтернатива: MinIO — S3 на своём сервере

Если не хочешь зависеть от облака, можно развернуть MinIO — open-source сервер с полной поддержкой S3 API.
Он лёгкий, ставится одной командой Docker, и идеально подходит для внутренних сервисов и dev-окружений:

docker run -p 9000:9000 -p 9001:9001 \
  -e MINIO_ROOT_USER=admin \
  -e MINIO_ROOT_PASSWORD=secret123 \
  quay.io/minio/minio server /data --console-address ":9001"

Заключение

S3-совместимые хранилища стали стандартом для работы с файлами. Они:

  • масштабируются без боли,
  • надёжно защищают данные,
  • легко интегрируются с CDN,
  • и поддерживаются множеством SDK.

Если тебе нужен быстрый способ реализовать загрузку изображений — начни с простого решения на Express + AWS SDK, а потом можешь перейти к presigned-upload и CDN-интеграции.

8 минут чтения
Средний рейтинг статьи — 4.7

Настроить мониторинг за 30 секунд

Надежные оповещения о даунтаймах. Без ложных срабатываний