Как реализовать загрузку и хранение файлов в S3-совместимых хранилищах
Загрузка изображений и файлов — одна из базовых задач любого веб-приложения. Вместо того чтобы хранить файлы прямо на сервере, современные проекты используют облачные 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, он будет работать и с этими платформами.
Как это устроено
- Приложение (backend) принимает файл от клиента.
- Оно либо:
- загружает файл само в хранилище через SDK,
- либо выдаёт клиенту pre-signed URL, по которому клиент может сам отправить файл напрямую в бакет.
- Хранилище возвращает ссылку на объект — обычно это публичный 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-интеграции.
Настроить мониторинг за 30 секунд
Надежные оповещения о даунтаймах. Без ложных срабатываний