Zum Inhalt springen
HexaTransfer
Zurück zum Blog
Technische Vertiefungen

Erstellen Sie Your Own Dateiübertragung Service: Dev Tutorial

Erstellen Sie a Datei transfer service from scratch. Backend API design, storage integration, und user interface development tutorial.

Einen eigenen Dateiübertragungsdienst zu bauen kostet zwischen 5 und 15 USD monatlich bei moderatem Traffic — wenn man die richtigen Entscheidungen bei Storage, Auth und Architektur trifft. Das BDSG und die DSGVO begünstigen Designs, die so wenig personenbezogene Daten wie möglich erheben: wer auf User-Accounts, E-Mail-Logs und IP-Adressen verzichtet, hat strukturell weniger Compliance-Aufwand und weniger Risiko gegenüber dem BfDI. Fünf Komponenten reichen: ein Frontend, das Dateien im Browser aufteilt und optional verschlüsselt, eine REST-API, die Upload-Sessions verwaltet und Storage-URLs vorsigniert, S3-kompatibler Objektspeicher, eine kleine Metadaten-Datenbank und ein TLS 1.3-Reverse-Proxy. Mit Node 22, Fastify und Vite lässt sich ein WeTransfer-ähnlicher Dienst in etwa 600 Codezeilen liefern.

Zuerst die Storage-Schicht wählen

Storage-Ökonomie macht oder bricht einen Dateiübertragungsdienst. AWS S3 berechnet 0,09 USD/GB Egress, was bedeutet, dass ein beliebter 2-GB-Download, 1.000-mal geteilt, allein 180 USD an Bandbreite kostet. Cloudflare R2 berechnet null für Egress und 0,015 USD/GB gespeichert. Backblaze B2 berechnet 0,01 USD/GB Egress und 0,006 USD/GB gespeichert. Für einen kostenlosen Transfer-Dienst ist R2 die offensichtliche Wahl. MinIO erlaubt On-Premises-Hosting mit S3-Semantik auf eigener Hardware.

Presigned URLs nutzen, sodass Clients direkt vom Storage hochladen und herunterladen, nie über den Server. Das hält die API CPU-gebunden statt bandbreitengebunden.

Die API-Oberfläche skizzieren

Die API klein halten. Fünf Endpunkte decken 95 Prozent des Bedarfs ab:

  • POST /sessions — Upload-Session erstellen, ID und vorsignierte Multipart-URLs zurückgeben
  • POST /sessions/:id/complete — Multipart-Upload abschließen, Share-URL zurückgeben
  • GET /shares/:id — Datei-Metadaten zurückgeben (Größe, Ablauf, verbleibende Downloads)
  • GET /shares/:id/download — eine vorsignierte Download-URL zurückgeben
  • DELETE /shares/:id — dem Besitzer erlauben, frühzeitig zu widerrufen (erfordert ein Revocation-Token)

POST /sessions aggressiv rate-limiten — 20 pro IP pro Stunde ist ausreichend. Alle Endpunkte geben JSON zurück, nutzen Cache-Control: no-store und setzen Content-Security-Policy auf den HTML-Seiten.

Das Backend mit Fastify verdrahten

Fastify liefert JSON-Schema-Validierung, vernünftiges Logging und 30.000 req/s pro Core. Ein minimales 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 };
});

10-GB-Obergrenze, 8-MB-Parts, 1-Stunde-URL-Gültigkeit. Nach dem eigenen Traffic-Mix anpassen.

Das Upload-UI bauen

Vite plus React plus null UI-Bibliotheken reichen für funktionierendes Drag-and-Drop in etwa 150 Zeilen. Das kritische Stück ist der Upload-Worker: einen dedizierten Web Worker spawnen, ihm das File-Handle und die Part-URLs übergeben und jeden Chunk mit 3-facher Parallelität PUT-en lassen. Der Main Thread bleibt responsive, Fortschritts-Updates kommen via 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 });
};

Ein Passwortfeld hinzufügen, das einen Schlüssel via PBKDF2 (600.000 Iterationen, SHA-256) ableitet und jeden Chunk durch AES-256-GCM wrappen lässt, bevor der PUT erfolgt. Das verwandelt den Dienst in einen E2EE-Dienst statt einen serverseitig verschlüsselten.

Das Datenbankschema gestalten

SQLite via better-sqlite3 bewältigt Zehn-Millionen-Zeilen ohne Probleme. Das Schema schlank halten:

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);

Keine User-Tabelle, keine E-Mail, keine IP-Logs. Das ist eine bewusste Entscheidung — sie macht DSGVO-Compliance fast trivial. Ein nächtlicher Cron löscht abgelaufene Zeilen und ihre S3-Objekte.

Share-Links und Downloads implementieren

Share-URLs sehen aus wie https://yourapp.com/f/abc123#k=<base64key>, wenn clientseitige Verschlüsselung hinzugefügt wurde. Das Fragment hält den Schlüssel aus Server-Logs heraus. Die Download-Seite holt /shares/abc123, erhält eine vorsignierte S3-URL, streamt Bytes direkt von R2, entschlüsselt im Browser und übergibt das Ergebnis an StreamSaver.js oder die File System Access API.

Content-Disposition: attachment; filename*=UTF-8''encoded-name setzen, damit der Browser einen Speicherdialog auslöst statt die Datei inline zu rendern. Das ist wichtig für PDFs, HTML-Dateien und SVG, die alle Scripts in einem Browser-Kontext ausführen können.

Gegen Missbrauch absichern

Dateiübertragungsdienste ziehen Missbraucher an. Drei Kategorien erwarten: Malware-Verteilung, Phishing-Payload-Hosting und urheberrechtsverletzende Uploads. Gegenmaßnahmen nach Wirkungsgrad: Uploads pro IP rate-limiten, eine Mindestgröße verlangen (unter 10 KB ist fast immer Schrott), SHA-256 des Ciphertexts berechnen und gegen eine Denylist bekannter schlechter Hashes prüfen, und ein reibungsloses Missbrauchsmeldeformular bereitstellen.

Günstig deployen und betreiben

Ein einzelner 10-USD-Hetzner CX22 (2 vCPU, 4 GB RAM) betreibt Fastify + SQLite + einen Caddy-Reverse-Proxy und bedient Tausende aktiver Nutzer. Caddy gibt automatisch TLS 1.3 via Let's Encrypt. Cloudflare davor für DDoS-Schutz und die kostenlose WAF. Mit Uptime Kuma (kostenlos, selbst gehostet) überwachen und in stdout loggen, das von journald erfasst wird.

Für geografische Redundanz SQLite mit Litestream zu R2 replizieren und eine zweite Region auf denselben Objektspeicher zeigen lassen. Failover ist DNS-seitig. Die gesamte DR-Geschichte passt auf eine Serviette.

HexaTransfer basiert auf genau diesem Ansatz — Fastify-API, R2-Storage, Web Crypto im Browser, SQLite-Metadaten, keine Accounts. Ende-zu-Ende-Verschlüsselung ist dabei kein Zusatz, sondern das Kerndesign.

Jetzt ausprobieren: https://hexatransfer.com — kostenlos, kein Account, bis zu 10 GB.

Große Dateien sicher mit Ende-zu-Ende-Verschlüsselung senden

Übertragen Sie Dateien bis zu 10 GB kostenlos mit Ende-zu-Ende-Verschlüsselung. Kein Konto erforderlich. Ihre Dateien werden in Ihrem Browser verschlüsselt, bevor sie hochgeladen werden — niemand sonst kann sie lesen.

Datei senden