Stocker et servir des fichiers dans le cloud : Azure Blob Storage en pratique
La gestion des fichiers dans un environnement microservices est plus complexe qu'il n'y paraît — limites d'upload, streaming, contrôle d'accès et durabilité doivent tous être conçus en amont. Ce guide couvre comment nous avons construit un système de gestion documentaire scalable sur Azure Blob Storage.
Gérer les téléchargements de fichiers dans une architecture microservices nécessite une solution de stockage robuste et scalable. Azure Blob Storage fournit un stockage d'objets de niveau entreprise qui s'intègre parfaitement avec NestJS. Ce guide vous montre comment construire un système de gestion de PDF prêt pour la production avec des opérations d'upload, téléchargement, mise à jour et suppression.
1. Pourquoi Azure Blob Storage ?
Lorsque vous construisez des applications qui gèrent des téléchargements de fichiers, vous avez plusieurs options : le système de fichiers local, les BLOBs en base de données, ou le stockage d'objets cloud. Voici pourquoi Azure Blob Storage se démarque :
- Scalabilité : Stockez des pétaoctets de données sans gérer d'infrastructure
- Rentabilité : Payez uniquement ce que vous utilisez, avec plusieurs niveaux de stockage
- CDN Global : Servez des fichiers depuis des points de présence dans le monde entier
- Sécurité : Chiffrement intégré, contrôle d'accès et certifications de conformité
- Intégration : Support SDK de premier ordre pour Node.js/TypeScript
Dans ce guide, nous allons construire un système où des entreprises peuvent uploader, consulter, mettre à jour et supprimer des documents PDF — tous stockés dans Azure Blob Storage et gérés via une architecture microservices NestJS.
2. Configuration & Installation
Installer les Dépendances
npm install @azure/storage-blob
npm install @nestjs/platform-express
npm install uuid
Variables d'Environnement
Créez un fichier .env avec vos identifiants Azure :
AZURE_STORAGE_CONNEXION_STRING=DefaultEndpointsProtocol=https;AccountName=...
AZURE_STORAGE_CONTAINER_NAME=company-documents
Note de Sécurité : Ne commitez jamais les chaînes de connexion dans le contrôle de version. Utilisez Azure Key Vault ou une gestion des secrets spécifique à l'environnement en production.
3. La Couche Service
Le service gère toutes les opérations Azure Blob Storage et communique avec d'autres microservices via RabbitMQ.
Architecture de Base
import { BlobServiceClient, BlockBlobClient } from '@azure/storage-blob';
import { Injectable, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class AzureBlobService {
constructor(
@Inject('AUTH') private authMicroservice: ClientProxy
) {}
private azureConnection = process.env.AZURE_STORAGE_CONNEXION_STRING;
getBlobClient(fileName: string, containerName: string): BlockBlobClient {
const blobServiceClient = BlobServiceClient.fromConnectionString(
this.azureConnection
);
const containerClient = blobServiceClient.getContainerClient(containerName);
return containerClient.getBlockBlobClient(fileName);
}
}
Opération d'Upload
La méthode d'upload génère un nom de fichier unique, uploade le buffer du fichier et retourne l'URL publique :
async upload(
file: Express.Multer.File,
containerName: string
): Promise {
// Générer un nom de fichier unique pour éviter les collisions
const uniqueFileName = uuidv4() + file.originalname;
const blobClient = this.getBlobClient(uniqueFileName, containerName);
// Uploader le buffer du fichier directement vers Azure
await blobClient.uploadData(file.buffer);
// Retourner l'URL publique
return blobClient.url;
}
Pattern Clé : Nous préfixons un UUID au nom de fichier original. Cela empêche les collisions de noms tout en préservant l'extension d'origine pour une détection correcte du type MIME.
Opération de Téléchargement (Streaming)
Au lieu de charger tout le fichier en mémoire, nous le streamons directement vers le client :
async getFile(fileName: string, containerName: string) {
const blobClient = this.getBlobClient(fileName, containerName);
const blobDownloaded = await blobClient.download();
// Retourner un flux lisible, pas un buffer
return blobDownloaded.readableStreamBody;
}
Pourquoi le Streaming ? Pour les gros PDF (10 Mo+), mettre tout le fichier en mémoire gaspille de la RAM et ralentit le temps de réponse. Le streaming envoie des morceaux au fur et à mesure qu'ils sont lus depuis Azure, réduisant la latence et l'utilisation mémoire.
Opération de Suppression
async deleteFile(fileName: string, containerName: string) {
const blobClient = this.getBlobClient(fileName, containerName);
await blobClient.deleteIfExists();
}
4. La Couche Contrôleur
Le contrôleur expose les endpoints REST pour les opérations sur les fichiers et s'intègre avec Swagger pour la documentation d'API.
Endpoint d'Upload
@Post('company/:companyId/upload')
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiBody({
schema: {
type: 'object',
properties: {
file: { type: 'string', format: 'binary' }
}
}
})
async uploadCompanyPdf(
@Param('companyId') companyId: string,
@UploadedFile() file: Express.Multer.File
) {
if (!file) {
throw new HttpException('No file provided', HttpStatus.BAD_REQUEST);
}
const fileUrl = await this.azureBlobService.upload(
file,
this.containerName
);
// Stocker les métadonnées en base de données via le microservice
await this.azureBlobService.uploadCompanyPdf({
fileUrl,
companyId,
fileName: file.originalname
});
return { message: 'PDF uploaded successfully' };
}
Décomposition du Pattern :
FileInterceptor('file')- Analyse le multipart/form-data@ApiConsumes- Indique à Swagger qu'il s'agit d'un upload de fichier@ApiBody- Affiche un champ de fichier dans l'interface Swagger- Uploader vers Azure, puis stocker les métadonnées en base de données
Endpoint de Téléchargement (Streaming)
@Get('company/:companyId')
async getCompanyPdf(
@Param('companyId') companyId: string,
@Res() res: Response
) {
// Récupérer les métadonnées du fichier depuis la base de données
const company = await this.azureBlobService.getCompanyPdf(companyId);
if (!company?.fileUrl) {
throw new HttpException('No PDF found', HttpStatus.NOT_FOUND);
}
// Extraire le nom du blob depuis l'URL et décoder
const blobName = company.fileUrl.split('/').pop();
const decodedBlobName = decodeURIComponent(blobName);
// Obtenir un flux lisible depuis Azure
const fileStream = await this.azureBlobService.getFile(
decodedBlobName,
this.containerName
);
// Définir les en-têtes pour l'affichage PDF en ligne
res.set({
'Content-Type': 'application/pdf',
'Content-Disposition': `inline; filename="${company.fileName}"`
});
// Piper le flux directement vers la réponse
fileStream.pipe(res);
}
Détail Critique : Nous utilisons decodeURIComponent() car les URLs Azure encodent les caractères spéciaux. Si le nom de fichier original était "Rapport (2024).pdf", le nom du blob devient "Rapport%20%282024%29.pdf".
Endpoint de Mise à Jour
Mettre à jour un fichier nécessite de supprimer l'ancien blob et d'uploader le nouveau :
@Put('company/:companyId/update')
@UseInterceptors(FileInterceptor('file'))
async updateCompanyPdf(
@Param('companyId') companyId: string,
@UploadedFile() file: Express.Multer.File
) {
const company = await this.azureBlobService.getCompanyPdf(companyId);
if (!company) {
throw new HttpException('Company not found', HttpStatus.NOT_FOUND);
}
// Supprimer l'ancien fichier d'Azure
if (company.fileUrl) {
const oldFileName = company.fileUrl.split('/').pop();
const decodedBlobName = decodeURIComponent(oldFileName);
await this.azureBlobService.deleteFile(decodedBlobName, this.containerName);
}
// Uploader le nouveau fichier
const newFileUrl = await this.azureBlobService.upload(file, this.containerName);
// Mettre à jour les métadonnées en base de données
await this.azureBlobService.updateCompanyPdf({
fileUrl: newFileUrl,
companyId
});
return { message: 'PDF updated successfully' };
}
5. Intégration Microservices
Dans une architecture microservices, la passerelle API (où les fichiers sont uploadés) est séparée du service Auth (où les métadonnées de l'entreprise sont stockées). Nous utilisons RabbitMQ pour la communication :
async uploadCompanyPdf(createResultDto: CreateResultDto): Promise {
const pattern = { cmd: 'createResultPdf' };
const payload = createResultDto;
const res = await this.authMicroservice
.send(pattern.cmd, payload)
.toPromise();
return res;
}
Flux :
- La passerelle API reçoit l'upload du fichier
- Uploader le fichier vers Azure Blob Storage
- Envoyer les métadonnées (fileUrl, companyId, fileName) au service Auth via RabbitMQ
- Le service Auth stocke les métadonnées dans PostgreSQL
- Retourner une réponse de succès
Ce pattern maintient les services découplés — la passerelle API n'a pas besoin d'un accès direct à la base de données.
6. Bonnes Pratiques en Production
Sécurité
- Utiliser des Tokens SAS : Pour un accès public temporaire, générez des Signatures d'Accès Partagé plutôt que de rendre les conteneurs publics
- Valider les Types de Fichiers : Vérifier les types MIME et les extensions avant l'upload
- Limites de Taille : Configurer la taille maximale de fichier dans NestJS (la valeur par défaut est 1 Mo)
- Contrôle d'Accès : Utiliser Azure RBAC pour limiter qui peut uploader/supprimer des blobs
Performance
- Streamer, Pas Mettre en Buffer : Toujours utiliser
readableStreamBodypour les téléchargements - Intégration CDN : Activer Azure CDN pour les fichiers fréquemment accédés
- Uploads Concurrents : Utiliser
Promise.all()pour les uploads en lot
Gestion des Erreurs
async upload(file: Express.Multer.File, containerName: string): Promise {
try {
const uniqueFileName = uuidv4() + file.originalname;
const blobClient = this.getBlobClient(uniqueFileName, containerName);
await blobClient.uploadData(file.buffer);
return blobClient.url;
} catch (error) {
console.error('Azure upload failed:', error);
throw new HttpException(
'Failed to upload file to storage',
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
Conclusion
Construire un système de gestion de fichiers prêt pour la production ne consiste pas seulement à uploader des fichiers — c'est à le faire correctement. Azure Blob Storage vous offre une infrastructure de niveau entreprise sans la surcharge opérationnelle, et NestJS fournit le cadre parfait pour construire des API scalables et maintenables par-dessus.
Les patterns que nous avons couverts résolvent de vrais défis en production :
- Les noms de fichiers basés sur UUID éliminent les conditions de course et les collisions dans les systèmes à fort trafic
- Les téléchargements en streaming maintiennent une empreinte mémoire constante, que vous serviez des fichiers de 1 Mo ou 100 Mo
- Le découplage microservices vous permet de mettre à l'échelle le stockage de fichiers indépendamment de votre logique métier
- Le décodage d'URL prévient ces bugs subtils qui n'apparaissent qu'avec des caractères spéciaux en production
Cette architecture passe à l'échelle. Que vous gériez 100 PDFs pour un petit SaaS ou des millions de documents pour une plateforme enterprise, ces patterns restent les mêmes. La beauté du stockage d'objets cloud est que vous ne redesignez pas quand vous montez en charge — vous payez simplement pour plus de stockage.
Commencez simplement : un conteneur, un service, upload/téléchargement basique. Ajoutez des tokens SAS quand vous avez besoin d'un accès temporaire. Intégrez un CDN quand la latence devient importante. N'ajoutez de complexité que quand vous en avez besoin. C'est ainsi que vous construisez des systèmes durables.
Écrit par

Tech Lead et Ingénieur Full Stack pilotant une équipe de 5 ingénieurs chez Fygurs (Paris, Remote) sur un SaaS cloud-native Azure. Diplômé de 1337 Coding School (42 Network / UM6P). Écrit sur l'architecture, l'infrastructure cloud et le leadership technique.