API de téléchargement et récupération de fichiers avec NestJS et MongoDB

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/mongoose
  • mongoose
  • multer (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.

Étiquettes: NestJS TypeScript MongoDB Mongoose multer

Publié le 27 octobre à 22h55