PAS7 Studio
Ілюстрація pipeline Bun.js, S3-compatible storage та Sharp
Технології27 серп. 2026 р.·4 хв читання·Оновлено 27 серп. 2026 р.

Bun.js + S3 + Sharp: завантаження та оптимізація зображень

Як побудувати безпечний image pipeline на Bun.js: presigned URL для S3-compatible storage, Sharp для resize/WebP та валідація файлів без перевантаження API.

Full-stack розробникиАвтори медіа-платформКоманди, що будують upload API

API на Bun спершу перевіряє тип і розмір майбутнього файла, видає короткоживучий presigned PUT URL, а браузер завантажує файл напряму в S3. Окремий worker або endpoint читає оригінал, створює WebP/thumbnail через Sharp і зберігає похідні об'єкти.

Великі файли не проходять через пам'ять API-сервера.
Клієнт не отримує AWS credentials — тільки обмежений URL.
Sharp нормалізує формат, розмір і якість до публікації.
Xin

Архітектура без зайвого проксіювання файлів

Bun залишається control plane, а object storage — data plane.

01

Підготувати upload

Клієнт надсилає filename, MIME type і розмір. API перевіряє allowlist та повертає key і presigned URL.

02

Завантажити напряму

Браузер виконує PUT у S3. Ваш Bun-процес не тримає байти у RAM і не стає вузьким місцем.

03

Обробити

Worker або внутрішній endpoint читає оригінал, Sharp створює thumbnail і webp-версію.

04

Опублікувати

У базі зберігаються тільки перевірені ключі, розміри, MIME type і статус обробки.

Файл іде напряму в storage, тому Bun обробляє контроль доступу та метадані, а не проксіює великі байти.

Скріншот секції architecture

Крок 1: встановлюємо AWS SDK та Sharp

01

Додайте залежності

BASH
bun add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner sharp

Sharp містить native-компонент. Перевірте, що target-платформа вашого Docker-образу відповідає платформі, для якої встановлено optional dependencies.

02

Задайте bucket

ENV
S3_REGION=eu-central-1
S3_BUCKET=media
S3_ENDPOINT=https://s3.example.com
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...

S3_ENDPOINT можна вказати для Cloudflare R2 або MinIO. Секретний ключ ніколи не відправляйте у frontend.

Крок 2: видаємо presigned PUT URL

URL має жити недовго, містити випадковий object key і бути прив'язаним до очікуваного Content-Type. Для production також перевіряйте розмір після upload через HEAD або подію storage.

TS
// src/storage.ts
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3 = new S3Client({
  region: Bun.env.S3_REGION!,
  endpoint: Bun.env.S3_ENDPOINT || undefined,
  forcePathStyle: Boolean(Bun.env.S3_ENDPOINT),
});

const allowed = new Set(["image/jpeg", "image/png", "image/webp"]);

export async function createUploadUrl(contentType: string) {
  if (!allowed.has(contentType)) throw new Error("Unsupported image type");
  const key = `originals/${crypto.randomUUID()}`;
  const command = new PutObjectCommand({
    Bucket: Bun.env.S3_BUCKET!,
    Key: key,
    ContentType: contentType,
    ServerSideEncryption: "AES256",
  });
  return { key, url: await getSignedUrl(s3, command, { expiresIn: 300 }) };
}

П'ять хвилин достатньо для звичайного upload і зменшує вікно для зловживань.

Крок 3: створюємо похідні версії через Sharp

Не довіряйте розширенню файла: реальний MIME type і декодування мають бути перевірені на сервері. Sharp читає image metadata та може відкинути небезпечні або пошкоджені дані до публікації.

TS
import sharp from "sharp";

export async function makeVariants(input: Buffer) {
  const image = sharp(input, { limitInputPixels: 40_000_000 });
  const metadata = await image.metadata();
  if (!metadata.width || !metadata.height) throw new Error("Invalid image");

  const thumbnail = await image
    .clone()
    .rotate()
    .resize({ width: 480, height: 480, fit: "cover" })
    .webp({ quality: 78 })
    .toBuffer();

  const preview = await image
    .clone()
    .rotate()
    .resize({ width: 1600, withoutEnlargement: true })
    .webp({ quality: 84 })
    .toBuffer();

  return { thumbnail, preview, width: metadata.width, height: metadata.height };
}

rotate() враховує EXIF-орієнтацію, а withoutEnlargement не роздуває маленькі зображення. Для AVIF додавайте окремий профіль після вимірювання CPU та розміру файлів.

Маршрут підготовки upload і callback після обробки

API не має вважати файл готовим одразу після видачі URL. Створіть запис зі статусом pending, а після успішного PUT або повідомлення worker-а переведіть його в ready.

TS
// src/routes/uploads.ts
import { Elysia, t } from "elysia";
import { createUploadUrl } from "../storage";

export const uploadRoutes = new Elysia({ prefix: "/uploads" })
  .post("/prepare", async ({ body, set }) => {
    const upload = await createUploadUrl(body.contentType);
    // Збережіть upload.key і userId у БД зі статусом pending.
    set.status = 201;
    return { ...upload, status: "pending" };
  }, {
    body: t.Object({
      contentType: t.Union([t.Literal("image/jpeg"), t.Literal("image/png"), t.Literal("image/webp")]),
    }),
  });

На frontend: PUT у повернутий URL з точним Content-Type, потім повідомлення POST /uploads/:id/complete. Worker повторно перевіряє object metadata перед Sharp.

Не приймайте від клієнта готову публічну URL-адресу. Публікуйте лише key, який створив сервер, і будуйте CDN URL централізовано.

Безпека та контроль витрат

Upload endpoint — це не просто форма. Обмеження мають бути на кожному етапі.

Обмежуйте розмір

Лімітуйте payload на API та перевіряйте фактичний Content-Length/розмір object після PUT.

Не приймайте довільні ключі

Генеруйте key на сервері з UUID і namespace користувача або проєкту.

Скануйте або модеруйте

Для публічного контенту додайте antivirus/moderation крок до зміни статусу на ready.

Додавайте lifecycle rules

Тимчасові originals і незавершені uploads мають автоматично видалятися через storage lifecycle.

Не робіть Sharp у request без ліміту

Важку обробку краще винести в чергу або окремий worker, щоб upload API залишався responsive.

Мінімальний тест для Sharp pipeline

Тестуйте не тільки HTTP-статус, а й результат декодування: формат, ширину та приблизний розмір. Так ви помітите випадкову зміну quality або зламану native-залежність після оновлення Docker image.

TS
import { expect, test } from "bun:test";
import sharp from "sharp";
import { makeVariants } from "./images";

test("creates a bounded WebP thumbnail", async () => {
  const input = await sharp({
    create: { width: 1200, height: 800, channels: 3, background: "#f97316" },
  }).png().toBuffer();
  const { thumbnail } = await makeVariants(input);
  const meta = await sharp(thumbnail).metadata();
  expect(meta.format).toBe("webp");
  expect(meta.width).toBe(480);
  expect(meta.height).toBe(480);
});

Запуск: bun test --coverage. Для integration-тесту storage використовуйте MinIO у Compose або окремий тестовий bucket з lifecycle cleanup.

Коли обрати цей підхід

Підходить

Аватари, каталоги, CMS, user-generated content і будь-які файли, де потрібні thumbnail та CDN-friendly WebP.

Потребує іншої схеми

Якщо потрібні відео, десятки гігабайтів або realtime-прогрес обробки, додайте чергу, multipart upload і спеціалізований media worker.

FAQ

Часті запитання

Чи потрібно пропускати файл через Bun?

Ні. Для великих файлів краще видавати presigned URL і завантажувати напряму в S3-compatible storage. Bun керує дозволом, метаданими та статусом обробки.

Чи працює Sharp на Bun?

Так, але Sharp має native-залежності, тому їх потрібно коректно встановити для цільової ОС і архітектури. Перевіряйте production Docker image окремим smoke test.

Перевірено: 27 серп. 2026 р.Актуально для: Bun 1.3+Актуально для: AWS SDK v3Актуально для: Sharp 0.34+Актуально для: Amazon S3 або S3-compatible storageПеревірено з: Bun runtimeПеревірено з: @aws-sdk/client-s3Перевірено з: @aws-sdk/s3-request-presignerПеревірено з: Sharp

Висновок

Опишіть задачу — перші 15 хвилин консультації безкоштовні.

Пов'язані статті

Скільки коштує розробка AI асистента у 2026: RAG чатбот, база знань, CRM, Telegram та підтримка
ai-assistants

Скільки коштує розробка AI асистента у 2026: RAG чатбот, база знань, CRM, Telegram та підтримка

Практичний гід для бізнесу: від чого залежить ціна розробки AI асистента у 2026 році, що входить у RAG чатбот, інтеграції з CRM, Telegram, guardrails, оцінювання, моніторинг і супровід.

AI для розробки лендінгів: де він реально прискорює запуск, а де псує конверсію
blogs

AI для розробки лендінгів: де він реально прискорює запуск, а де псує конверсію

Дослідження про використання AI у розробці лендінгів: v0, Webflow AI, Builder.io, Framer-подібні AI builders, генерація UX, copy, SEO, персоналізація, A/B тести, ризики шаблонності, безпеки, доступності та технічного боргу.

AI SEO / GEO у 2026: ваші наступні клієнти — не люди, а агенти
growth

AI SEO / GEO у 2026: ваші наступні клієнти — не люди, а агенти

Пошук зміщується від кліків до відповідей. Боти та AI-агенти сканують, цитують, рекомендують і дедалі частіше купують. Дізнайтесь, що таке AI SEO / GEO, чому класичного SEO вже недостатньо, і як PAS7 Studio допомагає брендам перемагати у «агентному» вебі.

Найпотужніший чіп від Apple? M5 Pro і M5 Max б'ють рекорди
blogs

Найпотужніший чіп від Apple? M5 Pro і M5 Max б'ють рекорди

Аналітичний розбір Apple M5 Pro і M5 Max станом на березень 2026 року. Пояснюємо, чому ці чіпи можна вважати найпотужнішими професійними ноутбучними SoC від Apple, як вони виглядають на тлі M4 Pro, M4 Max, M1 Pro, M1 Max і що показують у порівнянні з актуальними Intel та AMD.

Професійна розробка для вашого бізнесу

Створюємо сучасні веб-рішення та боти для бізнесу. Дізнайтеся, як ми можемо допомогти вам досягти цілей.