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 може зробити більше. Не краще: що насправді кажуть дослідження про розробку ігор

Generative AI входить у production ігор, але докази складніші за хайп. Розбираємо adoption, ставлення гравців, ризики якості та практичний workflow.

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 допомагає брендам перемагати у «агентному» вебі.

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

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