Téléverser des fichiers volumineux avec Multer et le chunked upload
Les applications web modernes manipulent quotidiennement des fichiers toujours plus imposants : vidéos en haute définition, jeux de données pour l'entraînement de modèles, sauvegardes complètes ou encore archives multimédias. Quand un utilisateur tente d'envoyer un document de plusieurs gigaoctets via un formulaire HTML classique, la requête HTTP dépasse souvent la taille maximale autorisée par les proxys, les pare-feu ou le serveur lui-même. La connexion s'interrompt alors brutalement, la mémoire du processus gonfle jusqu'à l'overflow, et l'expérience se termine par un message d'erreur générique qui laisse l'utilisateur perplexe.
Pour répondre à ce besoin, une technique éprouvée consiste à fragmenter le fichier original en plusieurs blocs indépendants que le navigateur envoie successivement au serveur. Ce découpage, désigné par les termes chunked upload ou téléversement par morceaux, permet de contourner les limites de taille des requêtes, de reprendre un transfert interrompu et d'offrir une barre de progression fiable. Multer, le middleware le plus utilisé dans l'écosystème Node.js pour traiter les fichiers entrants, se combine très bien avec cette approche à condition d'adapter légèrement son utilisation traditionnelle.
Dans un contexte où les fichiers téléversés alimentent souvent des pipelines d'analyse ou d'intelligence artificielle, comme on peut le voir sur helloml.org, la robustesse du transfert devient un enjeu critique. Maîtriser le découpage, la réception et l'assemblage des fragments permet de garantir que les données arrivent intactes au bon endroit, prêtes à être exploitées par les services en aval.
Pourquoi le téléversement par morceaux est devenu indispensable
Le protocole HTTP n'a pas été conçu à l'origine pour transporter des fichiers de plusieurs centaines de mégaoctets dans une seule requête. Lorsqu'un serveur Express reçoit un multipart/form-data, il doit généralement stocker l'ensemble du corps de la requête en mémoire ou dans un fichier temporaire avant de la transmettre à Multer pour analyse. Avec un fichier de 2 Go, cela représente vite un goulot d'étranglement pour le processus Node.js, dont la mémoire heap par défaut culmine à environ 1,7 Go sur la plupart des architectures 64 bits.
Au-delà de la mémoire, la durée de la requête constitue un autre facteur limitant. Plus le transfert dure, plus le risque de déconnexion réseau augmente, notamment sur les réseaux mobiles ou dans les zones à faible couverture. Le client ignore alors où en était le transfert et doit tout recommencer depuis le début, ce qui multiplie la bande passante consommée et la frustration de l'utilisateur. Le découpage en morceaux de taille fixe, typiquement entre 1 et 10 Mo, divise ce risque en unités indépendantes que le serveur acquitte une par une.
Cette granularité ouvre aussi la porte à des fonctionnalités avancées : reprise après interruption, calcul précis de la progression, parallélisation contrôlée des envois, ou encore validation incrémentale du contenu au fur et à mesure de sa réception. Les solutions de stockage objet comme S3 ou les systèmes de fichiers distribués s'intègrent d'autant mieux que les blocs manipulés restent de taille prévisible et uniforme.
Préparer l'environnement Node.js avec Multer
Avant de plonger dans la logique métier, il faut mettre en place un socle technique minimal. Commencez par initialiser un projet Node.js et installez les dépendances indispensables : express pour le serveur HTTP, multer pour la gestion des téléversements, ainsi que des utilitaires comme cors si votre frontend est servi depuis un domaine distinct. La commande classique npm install express multer cors suffit pour démarrer, mais vous pouvez également ajouter uuid afin de générer un identifiant unique par session d'upload, ainsi que busboy si vous avez besoin d'un contrôle plus fin sur le flux de données.
Multer propose plusieurs modes de stockage : memoryStorage conserve les fichiers en mémoire vive, diskStorage les écrit immédiatement sur le disque. Pour le chunked upload, memoryStorage reste pertinent car chaque fragment est de taille modeste, mais diskStorage devient intéressant si vous voulez éviter de saturer la RAM sous forte charge. La configuration de base consiste à instancier Multer avec des limites explicites sur la taille du fichier, le nombre de champs et le nombre de fichiers par requête, puis à l'attacher à une route Express dédiée aux fragments.
Pour aller plus loin dans l'industrialisation, la zone d'intégration dédiée aux développeurs regroupe des connecteurs prêts à l'emploi vers les principales plateformes cloud et services tiers. Vous pouvez par exemple y relier votre bucket S3, votre cluster MongoDB ou votre file de tâches asynchrones en une seule configuration, sans avoir à réécrire la même plomberie technique pour chaque projet.
Réception et assemblage des fragments côté serveur
Le cœur du dispositif réside dans la route Express qui reçoit chaque fragment. Plutôt que de laisser Multer interpréter directement un fichier complet, vous récupérez les métadonnées envoyées par le client : nom du fichier original, index du fragment, nombre total de blocs, taille du dernier bloc, et un identifiant de session unique. Ces informations peuvent transiter soit via des champs de formulaire supplémentaires, soit via des en-têtes HTTP personnalisés. Multer se charge alors de stocker uniquement le binaire du fragment dans un répertoire temporaire indexé par l'identifiant de session.
L'assemblage se fait de manière asynchrone après la réception du dernier fragment. Une approche courante consiste à utiliser un flux pipeline qui ouvre chaque fichier temporaire en lecture dans l'ordre des index, puis les concatène dans un fichier de destination. Pour ne pas saturer le disque, vous pouvez nettoyer automatiquement les fragments temporaires après assemblage réussi. Côté métadonnées, beaucoup d'équipes choisissent de stocker dans une base de données le statut de chaque upload, le nombre de blocs reçus, et l'emplacement final du fichier.
Le choix de la base de données influence directement la flexibilité de cette logique de suivi. Si vous hésitez entre une solution orientée documents et une base relationnelle pour stocker ces informations, l'analyse détaillée des bases de données orientées document et relationnelles vous donnera les clés pour faire un choix éclairé selon vos contraintes de volumétrie et de structuration des métadonnées.
Découper et envoyer le fichier depuis le navigateur
Côté frontend, le découpage repose principalement sur l'API Blob et sa méthode slice(). À partir d'un objet File récupéré via un input ou un drop, vous pouvez extraire des sous-ensembles de taille fixe grâce à file.slice(debut, fin). Chaque sous-ensemble est ensuite envoyé via fetch, avec un FormData contenant le binaire ainsi que les métadonnées attendues par le serveur. Pour paralléliser les envois sans saturer la bande passante, il est recommandé de limiter le nombre de requêtes simultanées à l'aide d'un petit ordonnanceur ou d'un pool de promesses contrôlé.
La gestion de l'état de l'upload mérite une attention particulière. Vous devez suivre la progression globale, les éventuelles erreurs par fragment, les tentatives de reprise et la liste des morceaux déjà acquittés. Cette logique peut rapidement devenir complexe si elle est gérée au moyen de hooks React maison ou de variables globales dispersées. Plusieurs bibliothèques d'état se disputent ce terrain, et la comparaison des principales librairies d'état pour React met en lumière les atouts de chacune pour ce type de besoin asynchrone. Les solutions modernes comme Zustand ou Jotai excellent dans la gestion d'événements concurrents et la mise à jour fine des interfaces de progression.
Pensez également à fournir à l'utilisateur un retour visuel clair : barre de progression globale, vitesse d'envoi estimée, bouton de pause et de reprise, gestion des annulations partielles. Ces détails transforment radicalement la perception d'un transfert long et rassurent l'utilisateur sur la fiabilité du mécanisme mis en place.
Sécurité, reprise sur erreur et bonnes pratiques
Un upload fragmenté expose une surface d'attaque plus large qu'un upload classique, car de nombreuses requêtes indépendantes doivent être validées à chaque étape. La première ligne de défense consiste à vérifier systématiquement le type MIME, l'extension et la taille cumulée du contenu reçu, et à rejeter toute session dont les métadonnées sont incohérentes, qu'il s'agisse d'un nombre de blocs négatif, d'un index hors limites ou d'un identifiant de session malformé. Une authentification solide doit également être imposée sur la route d'upload, idéalement associée à un jeton temporaire limité dans le temps.
La reprise sur erreur constitue un autre axe essentiel. Le client doit conserver l'état d'avancement côté navigateur, par exemple dans IndexedDB ou localStorage, afin de pouvoir relancer uniquement les fragments manquants en cas de coupure réseau. Côté serveur, prévoyez un mécanisme de nettoyage qui supprime périodiquement les sessions abandonnées depuis plus de 24 ou 48 heures, afin d'éviter l'accumulation de fichiers temporaires orphelins. Vous pouvez également calculer une somme de contrôle côté client et la vérifier côté serveur après assemblage pour garantir l'intégrité du fichier final.
Enfin, n'oubliez pas les bonnes pratiques d'observabilité : loggez chaque étape importante du parcours, exposez des métriques sur le débit moyen et la latence, et mettez en place des alertes en cas d'échec répété. Un système de chunked upload bien instrumenté devient alors un atout pour vos utilisateurs comme pour vos équipes d'exploitation, capable d'absorber des charges soutenues sans dégradation visible.
Vous souhaitez approfondir un aspect particulier de ce mécanisme ou découvrir comment intégrer ce type d'upload dans une architecture microservices distribuée ? Explorez les autres ressources publiées sur DéveloppeurWeb.Com pour continuer à faire grandir vos compétences back-end et front-end, et n'hésitez pas à mettre en pratique ces conseils sur vos propres projets dès aujourd'hui.