AWS SDK for JavaScript v3 работает в Node.js и в браузерах. Примеры рассчитаны на Node.js 18+ и ES-модули (файлы .mjs или "type": "module").
Установка
npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
Ключи доступа
Передавайте ключи через переменные окружения — SDK прочитает их автоматически. Никогда не храните ключи в исходном коде и в репозитории.
export AWS_ACCESS_KEY_ID=<Access Key>
export AWS_SECRET_ACCESS_KEY=<Secret Key>
Создание клиента
// s3.mjs
import { S3Client } from "@aws-sdk/client-s3";
export const s3 = new S3Client({
region: "default",
endpoint: "https://s3.uzcstorage.uz",
forcePathStyle: true,
requestChecksumCalculation: "WHEN_REQUIRED",
responseChecksumValidation: "WHEN_REQUIRED",
maxAttempts: 5,
});
export const Bucket = "my-bucket";
Основные операции
import { readFile } from "node:fs/promises";
import {
PutObjectCommand, GetObjectCommand, CopyObjectCommand,
DeleteObjectCommand, DeleteObjectsCommand,
} from "@aws-sdk/client-s3";
import { s3, Bucket } from "./s3.mjs";
// Upload a file
await s3.send(new PutObjectCommand({
Bucket, Key: "docs/report.pdf",
Body: await readFile("report.pdf"),
ContentType: "application/pdf",
}));
// Read an object as a string (small objects only)
const obj = await s3.send(new GetObjectCommand({ Bucket, Key: "docs/hello.txt" }));
console.log(await obj.Body.transformToString());
// Copy and delete
await s3.send(new CopyObjectCommand({ Bucket, Key: "archive/report.pdf", CopySource: `${Bucket}/docs/report.pdf` }));
await s3.send(new DeleteObjectCommand({ Bucket, Key: "docs/report.pdf" }));
// Delete up to 1,000 objects in one request
await s3.send(new DeleteObjectsCommand({
Bucket, Delete: { Objects: [{ Key: "tmp/a.txt" }, { Key: "tmp/b.txt" }] },
}));
Список всех объектов
Один запрос возвращает не больше 1 000 объектов. Чтобы получить все объекты с префиксом, используйте пагинатор:
import { paginateListObjectsV2 } from "@aws-sdk/client-s3";
for await (const page of paginateListObjectsV2({ client: s3 }, { Bucket, Prefix: "docs/" })) {
for (const obj of page.Contents ?? []) console.log(obj.Key, obj.Size);
}
Большие файлы
Upload из @aws-sdk/lib-storage делит файл на части, загружает их параллельно и сообщает о прогрессе:
import { createReadStream } from "node:fs";
import { Upload } from "@aws-sdk/lib-storage";
const upload = new Upload({
client: s3,
params: { Bucket, Key: "backups/backup.tar.gz", Body: createReadStream("backup.tar.gz") },
partSize: 64 * 1024 * 1024,
queueSize: 4,
});
upload.on("httpUploadProgress", (p) => console.log(`${p.loaded} / ${p.total ?? "?"} bytes`));
await upload.done();
Потоковая передача
Чтобы не загружать большой объект в память целиком, читайте и записывайте его частями:
import { createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
const { Body } = await s3.send(new GetObjectCommand({ Bucket, Key: "media/video.mp4" }));
await pipeline(Body, createWriteStream("video-copy.mp4"));
Для загрузки из потока используйте Upload (см. выше): PutObjectCommand требует заранее известную длину потока.
Presigned URL
Presigned URL дает временный доступ к объекту без передачи ключей. Ссылка на загрузку позволяет браузеру или другому сервису загрузить файл прямо в бакет — см. Presigned URL и CORS.
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
// Download link, valid for 1 hour
const downloadUrl = await getSignedUrl(s3, new GetObjectCommand({ Bucket, Key: "docs/report.pdf" }), { expiresIn: 3600 });
// Upload link, valid for 10 minutes
const uploadUrl = await getSignedUrl(
s3,
new PutObjectCommand({ Bucket, Key: "uploads/photo.jpg", ContentType: "image/jpeg" }),
{ expiresIn: 600 },
);
Загрузка из браузера
Ключи должны оставаться на сервере. Сервер создает ссылку на загрузку, а браузер отправляет файл прямо в бакет, не пропуская его через ваш сервер:
Сервер (Express):
import express from "express";
import { randomUUID } from "node:crypto";
const app = express();
app.get("/upload-url", async (req, res) => {
const type = String(req.query.type || "application/octet-stream");
const Key = `uploads/${randomUUID()}`;
const url = await getSignedUrl(s3, new PutObjectCommand({ Bucket, Key, ContentType: type }), { expiresIn: 300 });
res.json({ url, key: Key });
});
app.listen(3000);
Браузер:
async function uploadFile(file) {
const r = await fetch(`/upload-url?type=${encodeURIComponent(file.type)}`);
const { url, key } = await r.json();
const put = await fetch(url, { method: "PUT", headers: { "Content-Type": file.type }, body: file });
if (!put.ok) throw new Error(`Upload failed: ${put.status}`);
return key;
}
Важно
Для загрузки из браузера разрешите домен сайта и метод PUT в правилах CORS бакета.
Ошибки и повторные попытки
Самые частые ошибки — отсутствующий объект (NoSuchKey или 404 для HeadObject) и нет доступа (AccessDenied, 403). Сетевые ошибки и ответы 5xx повторяются автоматически; число попыток задается при создании клиента.
import { HeadObjectCommand } from "@aws-sdk/client-s3";
async function objectExists(Key) {
try {
await s3.send(new HeadObjectCommand({ Bucket, Key }));
return true;
} catch (e) {
if (e.name === "NotFound" || e.$metadata?.httpStatusCode === 404) return false;
throw e;
}
}
try {
await s3.send(new GetObjectCommand({ Bucket, Key: "missing.txt" }));
} catch (e) {
if (e.name === "NoSuchKey") console.log("Object not found");
else if (e.name === "AccessDenied") console.log("Check the keys and the bucket policy");
else throw e;
}