Przejdź do treści
HexaTransfer
Wróć do bloga
Zagadnienia techniczne

Stwórz Your Own Prześlij plików Service: Dev Poradnik

Zbuduj a plik transfer service from scratch. Backend API design, storage integration, i user interface development tutorial.

Budując własną usługę transferu plików, musisz spełnić wymogi RODO dotyczące minimalizacji danych i bezpieczeństwa przetwarzania — co oznacza, że architektura techniczna i zgodność prawna idą w parze od pierwszego dnia. Do zbudowania własnej usługi potrzebujesz pięciu elementów: frontendu krojącego i opcjonalnie szyfrującego pliki w przeglądarce, REST API generującego sesje uploadu i presignowane URL-e storage, obiektu storage kompatybilnego z S3 (Cloudflare R2, Backblaze B2 lub MinIO), małej bazy metadanych (SQLite lub Postgres) i odwrotnego proxy TLS 1.3. Z Node 22, Fastify i Vite możesz dostarczyć usługę w stylu WeTransfer w około 600 liniach kodu i hostować całość za 5–15 dolarów miesięcznie przy umiarkowanym ruchu.

Wybierz najpierw warstwę storage

Ekonomika storage decyduje o powodzeniu lub porażce usługi transferu plików. AWS S3 pobiera 0,09 USD za GB ruchu wychodzącego — popularne pobieranie 2 GB współdzielone 1000 razy kosztuje 180 USD samej przepustowości. Cloudflare R2 pobiera zero za ruch wychodzący i 0,015 USD za GB przechowywania. Backblaze B2 pobiera 0,01 USD za GB ruchu wychodzącego i 0,006 USD za GB przechowywania. Dla bezpłatnej usługi transferu R2 jest oczywistym wyborem. MinIO pozwala na self-hosting semantyki S3 na własnym sprzęcie, co działa jeśli masz już przepustowość i dyski.

Używaj presignowanych URL-i, żeby klienci przesyłali i pobierali bezpośrednio ze storage, nigdy przez twój serwer. To utrzymuje twoje API związane z CPU zamiast przepustowością.

Szkicowanie powierzchni API

Utrzymuj API małe. Pięć endpointów pokrywa 95% tego, czego potrzebujesz:

  • POST /sessions — tworzy sesję uploadu, zwraca ID i presignowane URL-e multipart
  • POST /sessions/:id/complete — finalizuje upload wieloczęściowy, zwraca URL udostępniania
  • GET /shares/:id — zwraca metadane pliku (rozmiar, termin ważności, pozostałe pobrania)
  • GET /shares/:id/download — zwraca presignowany URL pobierania
  • DELETE /shares/:id — pozwala właścicielowi cofnąć wcześniej (wymaga tokenu odwołania)

Agresywnie ogranicz POST /sessions — 20 na IP na godzinę wystarczy. Wszystkie endpointy zwracają JSON, używają Cache-Control: no-store i ustawiają Content-Security-Policy na stronach HTML.

Podłączanie backendu z Fastify

Fastify daje walidację schematu JSON, sensowne logowanie i 30k żądań/s na rdzeń bez wysiłku. Minimalny POST /sessions:

fastify.post('/sessions', {
  schema: {
    body: {
      type: 'object', required: ['size'],
      properties: {
        size: { type: 'integer', minimum: 1, maximum: 10737418240 },
        contentType: { type: 'string', maxLength: 120 }
      }
    }
  }
}, async (req) => {
  const id = nanoid(16);
  const key = `blobs/${id}`;
  const upload = await s3.createMultipartUpload({
    Bucket: 'transfers', Key: key, ContentType: req.body.contentType
  });
  const partCount = Math.ceil(req.body.size / (8 * 1024 * 1024));
  const urls = await Promise.all(
    Array.from({ length: partCount }, (_, i) =>
      getSignedUrl(s3, new UploadPartCommand({
        Bucket: 'transfers', Key: key, UploadId: upload.UploadId, PartNumber: i + 1
      }), { expiresIn: 3600 }))
  );
  db.prepare('INSERT INTO sessions VALUES (?, ?, ?, ?)')
    .run(id, upload.UploadId, key, Date.now());
  return { id, uploadId: upload.UploadId, partUrls: urls };
});

Pułap 10 GB, części 8 MB, ważność URL 1 godzina. Dostosuj do swojego miksu ruchu.

Budowanie UI uploadu

Vite plus React plus zero biblioteki UI daje działający drag-and-drop w około 150 liniach. Kluczowym elementem jest worker uploadu: uruchom dedykowany Web Worker, przekaż mu uchwyt File i URL-e części, i niech wykonuje PUT każdego fragmentu z 3-krotną współbieżnością. Główny wątek pozostaje responsywny, aktualizacje postępu przychodzą przez postMessage.

// worker.js
self.onmessage = async ({ data: { file, partUrls } }) => {
  const partSize = 8 * 1024 * 1024;
  const etags = [];
  for (let i = 0; i < partUrls.length; i++) {
    const blob = file.slice(i * partSize, (i + 1) * partSize);
    const res = await fetch(partUrls[i], { method: 'PUT', body: blob });
    etags[i] = res.headers.get('etag');
    self.postMessage({ type: 'progress', done: i + 1, total: partUrls.length });
  }
  self.postMessage({ type: 'done', etags });
};

Dodaj pole hasła wyprowadzające klucz przez PBKDF2 (600 000 iteracji, SHA-256) i przepuszczające każdy fragment przez AES-256-GCM przed PUT. To zamienia twoją usługę w usługę E2EE zamiast szyfrowanej po stronie serwera.

Projektowanie schematu bazy danych

SQLite przez better-sqlite3 obsługuje dziesiątki milionów wierszy bez zacięcia. Utrzymuj schemat szczupły:

CREATE TABLE sessions (
  id TEXT PRIMARY KEY, upload_id TEXT, object_key TEXT, created_at INTEGER
);
CREATE TABLE shares (
  id TEXT PRIMARY KEY, object_key TEXT, size_bytes INTEGER,
  content_type TEXT, expires_at INTEGER, max_downloads INTEGER,
  download_count INTEGER DEFAULT 0, revocation_token TEXT
);
CREATE INDEX idx_shares_expires ON shares(expires_at);

Bez tabeli użytkowników, bez emaila, bez logów IP. To świadomy wybór — sprawia, że zgodność z RODO staje się prawie trywialna, co doceni każdy inspektor UODO. Nocny cron usuwa wygasłe wiersze i ich obiekty S3:

const expired = db.prepare('SELECT object_key FROM shares WHERE expires_at < ?').all(Date.now());
for (const { object_key } of expired) {
  await s3.deleteObject({ Bucket: 'transfers', Key: object_key });
}
db.prepare('DELETE FROM shares WHERE expires_at < ?').run(Date.now());

Implementacja linków udostępniania i pobierania

URL-e udostępniania wyglądają jak https://yourapp.com/f/abc123#k=<base64key> gdy dodasz szyfrowanie po stronie klienta. Fragment trzyma klucz poza logami serwera. Strona pobierania pobiera /shares/abc123, dostaje presignowany URL S3, strumieniuje bajty bezpośrednio z R2, deszyfruje w przeglądarce i przekazuje wynik do StreamSaver.js lub File System Access API.

Ustaw Content-Disposition: attachment; filename*=UTF-8''encoded-name, żeby przeglądarka wywołała okno zapisu zamiast renderowania pliku inline. Ma to znaczenie dla PDF-ów, plików HTML i SVG, które mogą wykonywać skrypty w kontekście przeglądarki.

Hartowanie przed nadużyciami

Usługi transferu plików przyciągają nadużycia. Oczekuj trzech kategorii: dystrybucja złośliwego oprogramowania, hosting ładunków phishingowych i naruszające prawa autorskie uploady. Środki zaradcze według wpływu: ogranicz uploady na IP, wymagaj minimalnego rozmiaru (poniżej 10 KB to prawie zawsze śmieć), oblicz SHA-256 szyfrogramu i sprawdź go względem listy odmów znanych złych hashów, i zapewnij formularz zgłaszania nadużyć z minimalnym tarciem.

Wdrożenie i tanie prowadzenie

Jeden Hetzner CX22 (2 vCPU, 4 GB RAM) uruchamia Fastify + SQLite + odwrotne proxy Caddy i obsługuje tysiące aktywnych użytkowników. Caddy zapewnia TLS 1.3 automatycznie przez Let's Encrypt. Postaw Cloudflare przed nim dla ochrony DDoS i darmowego WAF. Monitoruj przez Uptime Kuma (za darmo, self-hosted) i loguj do stdout przechwyconego przez journald.

Dla geograficznej redundancji replikuj SQLite przez Litestream do R2 i skieruj drugi region na ten sam object store. Failover jest na poziomie DNS. Cała historia DR mieści się na serwetce.

Całe powyższe podejście odzwierciedla sposób budowy HexaTransfer — API Fastify, storage R2, Web Crypto w przeglądarce, metadane SQLite, bez kont. Wypróbuj na https://hexatransfer.com — za darmo, bez konta, maks. 10 GB.

Czego nie budować do wersji 1

Nie buduj kont, zespołów ani folderów w v1. Nie dodawaj dostarczania emaila. Nie implementuj podglądu wideo ani miniatur obrazów — oba zmuszają serwer do oglądania tekstu jawnego. Nie wypuszczaj aplikacji mobilnej przed wersją webową solidnie działającą na mobilnym Safari. Creep funkcji, który zabił wczesne klony transferu plików, był zawsze tym samym wzorcem: konta, potem limity storage, potem płatność, potem plany zespołowe — i nagle konkurujesz z Dropboxem zamiast dostarczać szybkie, prywatne narzędzie transferu.

Wysyłaj duże pliki bezpiecznie z szyfrowaniem end-to-end

Przesyłaj pliki do 10 GB za darmo z szyfrowaniem end-to-end. Bez rejestracji. Twoje pliki są szyfrowane w przeglądarce przed przesłaniem — nikt inny nie może ich odczytać.

Wyślij plik