Mise en place d’une interface de gestion de fichiers
Nous construisons une API NestJS capable de recevoir des fichiers via multipart/form-data, de les stocker sur le disque et d’enregistrer les métadonnées dans MongoDB. Un point d’accès pour le téléchargement ultérieur est également fourni.
Dépendances principales
@nestjs/common@nestjs/platform-express@nestjs/mongoosemongoosemulter(intégré à Nest via@nestjs/platform-express)
Installation groupée :
npm install @nestjs/common @nestjs/platform-express @nestjs/mongoose mongoose
Configuration du module
Créez un module dédié StockageModule qui importe Mongoose, Multer et le contrôleur.
import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
import { MongooseModule } from '@nestjs/mongoose';
import { PieceJointe, PieceJointeSchema } from './piece-jointe.schema';
import { PieceJointeService } from './piece-jointe.service';
import { StockageController } from './stockage.controller';
@Module({
imports: [
MongooseModule.forFeature([{ name: PieceJointe.name, schema: PieceJointeSchema }]),
MulterModule.register({
dest: './uploads', // dossier temporaire (nous utiliserons une destination dynamique)
}),
],
controllers: [StockageController],
providers: [PieceJointeService],
})
export class StockageModule {}
Schéma Mongoose
Le document PieceJointe mémorise le nom d’origine et l’emplacement du fichier sauvegardé.
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
@Schema({ collection: 'documents' })
export class PieceJointe extends Document {
@Prop()
nomOriginal: string;
@Prop()
cheminServeur: string;
@Prop()
dateAjout: Date;
}
export const PieceJointeSchema = SchemaFactory.createForClass(PieceJointe);
Service de persistance
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { PieceJointe } from './piece-jointe.schema';
@Injectable()
export class PieceJointeService {
constructor(
@InjectModel(PieceJointe.name) private modelePieceJointe: Model<PieceJointe>,
) {}
async enregistrer(fichier: Express.Multer.File, cheminFinal: string): Promise<PieceJointe> {
const nouveau = new this.modelePieceJointe({
nomOriginal: fichier.originalname,
cheminServeur: cheminFinal,
dateAjout: new Date(),
});
return nouveau.save();
}
async lister(): Promise<PieceJointe[]> {
return this.modelePieceJointe.find().exec();
}
async obtenirParId(id: string): Promise<PieceJointe | null> {
return this.modelePieceJointe.findById(id).exec();
}
}
Contrôleur
Le contrôleur expose deux routes : POST /fichiers pour l’envoi et GET /fichiers/:id pour le téléchargement.
import {
Controller,
Post,
Get,
Param,
UploadedFile,
UseInterceptors,
Res,
NotFoundException,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname, join } from 'path';
import { Response } from 'express';
import { PieceJointeService } from './piece-jointe.service';
// Création d'un dossier par date
const dossierParDate = (): string => {
const maintenant = new Date();
const an = maintenant.getFullYear();
const mois = String(maintenant.getMonth() + 1).padStart(2, '0');
const jour = String(maintenant.getDate()).padStart(2, '0');
return `${an}-${mois}-${jour}`;
};
@Controller('fichiers')
export class StockageController {
constructor(private readonly service: PieceJointeService) {}
@Post()
@UseInterceptors(
FileInterceptor('document', {
storage: diskStorage({
destination: (req, file, cb) => {
const dossier = `./uploads/${dossierParDate()}`;
// on fait confiance à multer pour créer le dossier
cb(null, dossier);
},
filename: (req, file, cb) => {
const suffixe = Date.now() + extname(file.originalname);
cb(null, suffixe);
},
}),
}),
)
async ajouterFichier(@UploadedFile() fichier: Express.Multer.File) {
if (!fichier) throw new Error('Aucun fichier reçu');
const cheminRelatif = `uploads/${dossierParDate()}/${fichier.filename}`;
const enregistrement = await this.service.enregistrer(fichier, cheminRelatif);
return {
id: enregistrement._id,
nomOriginal: fichier.originalname,
lien: `/fichiers/${enregistrement._id}`,
};
}
@Get(':id')
async telecharger(@Param('id') id: string, @Res() reponse: Response) {
const piece = await this.service.obtenirParId(id);
if (!piece) throw new NotFoundException('Document introuvable');
const cheminAbsolu = join(process.cwd(), piece.cheminServeur);
reponse.download(cheminAbsolu, piece.nomOriginal);
}
}
Accès statique (optionnel)
Si vous souhaitez servir les ficheirs directement par URL, activez ServeStaticModule.
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(process.cwd(), 'uploads'),
serveRoot: '/media',
}),
StockageModule,
],
})
export class AppModule {}
Le code ci‑dessus peut être intégré tel quel dans un projet NestJS après avoir configuré la connexion Mongoose. Adaptez les chemins selon votre environnement.