Créez votre propre service de transfert de fichiers : tutoriel développeur
Construisez un service de transfert de fichiers de zéro. Conception d'API backend, intégration du stockage et tutoriel de développement de l'interface utilisateur.
Pour construire votre propre service de transfert de fichiers, vous avez besoin de cinq éléments : un frontend qui découpe et optionnellement chiffre les fichiers dans le navigateur, une API REST qui crée des sessions d'upload et présigne des URL de stockage, un stockage objet compatible S3 (Cloudflare R2, Backblaze B2, ou MinIO), une petite base de données de métadonnées (SQLite ou Postgres), et un reverse proxy TLS 1.3. Avec Node 22, Fastify et Vite, vous pouvez livrer un service de type WeTransfer en environ 600 lignes de code et héberger l'ensemble pour 5 à 15 € par mois à trafic modéré. Du point de vue RGPD, l'absence de tables utilisateurs et l'absence de journaux IP rendent la conformité presque triviale dès le départ.
Choisir d'abord la couche de stockage
Les économies de stockage font ou défont un service de transfert de fichiers. AWS S3 facture 0,09 $ par Go d'egress, ce qui signifie qu'un téléchargement populaire de 2 Go partagé 1 000 fois vous coûte 180 $ en bande passante seule. Cloudflare R2 facture zéro pour l'egress et 0,015 $ par Go stocké. Backblaze B2 facture 0,01 $ par Go d'egress et 0,006 $ par Go stocké. Pour un service de transfert niveau gratuit, R2 est le choix évident. MinIO vous permet de vous auto-héberger avec la sémantique S3 sur votre propre matériel, ce qui fonctionne si vous avez déjà de la bande passante et des disques inutilisés.
Utilisez des URL présignées pour que les clients uploadent et téléchargent directement depuis le stockage, sans jamais passer par votre serveur. Cela maintient votre API liée au CPU plutôt qu'à la bande passante.
Esquisser la surface d'API
Gardez l'API petite. Cinq endpoints couvrent 95 % de ce dont vous avez besoin :
POST /sessions— créer une session d'upload, retourner un ID et des URL multipart présignéesPOST /sessions/:id/complete— finaliser un upload multipart, retourner une URL de partageGET /shares/:id— retourner les métadonnées du fichier (taille, expiration, téléchargements restants)GET /shares/:id/download— retourner une URL de téléchargement présignéeDELETE /shares/:id— permettre au propriétaire de révoquer tôt (nécessite un token de révocation)
Limitez agressivement le débit sur POST /sessions — 20 par IP par heure suffit. Tous les endpoints retournent du JSON, utilisent Cache-Control: no-store, et définissent Content-Security-Policy sur les pages HTML.
Câbler le backend avec Fastify
Fastify vous donne la validation de schéma JSON, des logs sensibles, et 30 000 req/s par cœur sans effort. Un minimal 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 };
});
Plafond à 10 Go, parties de 8 Mo, validité des URL d'1 heure. Ajustez selon votre mix de trafic.
Construire l'interface d'upload
Vite plus React plus zéro bibliothèque UI vous amène à un glisser-déposer fonctionnel en environ 150 lignes. La pièce critique est le worker d'upload : créez un Web Worker dédié, passez-lui le handle File et les URL des parties, et faites-lui PUT chaque chunk avec une concurrence à 3 voies. Le thread principal reste réactif, les mises à jour de progression arrivent 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 });
};
Ajoutez un champ mot de passe qui dérive une clé via PBKDF2 (600 000 itérations, SHA-256) et passe chaque chunk à travers AES-256-GCM avant le PUT. Cela transforme votre service en service avec chiffrement de bout en bout plutôt que chiffrement côté serveur.
Concevoir le schéma de base de données
SQLite via better-sqlite3 gère des dizaines de millions de lignes sans sourciller. Gardez le schéma lean :
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);
Aucune table utilisateur, aucun email, aucun log IP. C'est un choix délibéré — il rend la conformité RGPD presque triviale. Un cron quotidien supprime les lignes expirées et leurs objets 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());
Implémenter les liens de partage et les téléchargements
Les URL de partage ressemblent à https://yourapp.com/f/abc123#k=<base64key> quand vous avez ajouté le chiffrement côté client. Le fragment garde la clé hors des logs serveur. La page de téléchargement récupère /shares/abc123, obtient une URL S3 présignée, streame les octets directement depuis R2, déchiffre dans le navigateur, et passe le résultat à StreamSaver.js ou à l'API File System Access.
Définissez Content-Disposition: attachment; filename*=UTF-8''nom-encodé pour que le navigateur déclenche une boîte de dialogue d'enregistrement plutôt que de rendre le fichier en ligne. Cela compte pour les PDF, les fichiers HTML et les SVG, qui peuvent tous exécuter des scripts dans un contexte navigateur.
Renforcer contre les abus
Les services de transfert de fichiers attirent les abuseurs. Attendez-vous à trois catégories : distribution de malwares, hébergement de charges de phishing, et uploads portant atteinte aux droits d'auteur. Atténuations par ordre d'impact : limitez les uploads par IP, exigez une taille minimale (sous 10 Ko c'est presque toujours du spam), calculez un SHA-256 du texte chiffré et vérifiez contre une liste de refus de hachages connus mauvais, et fournissez un formulaire de signalement d'abus sans friction. ClamAV sur le contenu déchiffré ne fonctionne que quand vous avez la clé, ce qui invalide le chiffrement de bout en bout — la bonne réponse est des limites de débit agnostiques au contenu plus des suppressions réactives.
Déployer et faire tourner à faible coût
Un seul Hetzner CX22 à 10 € (2 vCPU, 4 Go RAM) fait tourner Fastify + SQLite + un reverse proxy Caddy et gère des milliers d'utilisateurs actifs. Caddy vous donne TLS 1.3 automatiquement via Let's Encrypt. Mettez Cloudflare devant pour la protection DDoS et le WAF gratuit. Surveillez avec Uptime Kuma (gratuit, auto-hébergé) et loggez sur stdout capturé par journald.
Pour la redondance géographique, répliquez SQLite avec Litestream vers R2, et pointez une seconde région sur le même object store. Le basculement est au niveau DNS. Toute votre stratégie de reprise après sinistre tient sur une serviette.
L'approche globale ci-dessus reflète la manière dont HexaTransfer est construit — API Fastify, stockage R2, Web Crypto dans le navigateur, métadonnées SQLite, sans comptes.
Ce qu'il faut ignorer jusqu'à ce que vous en ayez besoin
N'incluez pas de comptes, d'équipes ou de dossiers en v1. N'ajoutez pas de livraison email. N'implémentez pas de prévisualisation vidéo ou de vignettes d'images (les deux obligent le serveur à voir le texte en clair). Ne livrez pas d'application mobile avant que la version web soit parfaitement solide sur Mobile Safari. La dérive fonctionnelle qui a tué les premiers clones de services de transfert de fichiers suivait toujours le même schéma : comptes, puis quotas de stockage, puis paiement, puis plans d'équipe — et soudain vous êtes en concurrence avec Dropbox plutôt que de livrer un outil de transfert rapide et privé. Gardez la surface petite et la cryptographie solide.
Essayez-le sur https://hexatransfer.com — gratuit, sans compte, 10 Go maximum.
Envoyez vos fichiers volumineux en toute sécurité avec le chiffrement de bout en bout
Transférez des fichiers jusqu'à 10 Go gratuitement avec le chiffrement de bout en bout. Aucun compte requis. Vos fichiers sont chiffrés dans votre navigateur avant l'envoi — personne d'autre ne peut les lire.
Envoyer un fichier