Die Verarbeitung von Datei-Uploads ist ein häufiger Anwendungsfall in modernen Webanwendungen. NestJS bietet durch die Integration mit Multer eine elegante Lösung für File Uploads und deren Verarbeitung.
Multer ist die Standard-Middleware für die Behandlung von
multipart/form-data in NestJS. Die Installation erfolgt
über npm:
npm install @nestjs/platform-express multer
npm install @types/multer --save-devimport {
Controller,
Post,
UploadedFile,
UseInterceptors
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { Express } from 'express';
@Controller('upload')
export class UploadController {
@Post('single')
@UseInterceptors(FileInterceptor('file'))
uploadSingle(@UploadedFile() file: Express.Multer.File) {
return {
filename: file.filename,
originalname: file.originalname,
size: file.size,
mimetype: file.mimetype
};
}
@Post('multiple')
@UseInterceptors(FilesInterceptor('files', 10))
uploadMultiple(@UploadedFiles() files: Express.Multer.File[]) {
return files.map(file => ({
filename: file.filename,
size: file.size
}));
}
}import { MulterModule } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname } from 'path';
@Module({
imports: [
MulterModule.register({
storage: diskStorage({
destination: './uploads',
filename: (req, file, callback) => {
const name = file.originalname.split('.')[0];
const fileExtName = extname(file.originalname);
const randomName = Array(4)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('');
callback(null, `${name}-${randomName}${fileExtName}`);
},
}),
limits: {
fileSize: 5 * 1024 * 1024, // 5MB
},
}),
],
controllers: [UploadController],
})
export class UploadModule {}Die Validierung von hochgeladenen Dateien ist essentiell für die Sicherheit der Anwendung.
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
@Injectable()
export class FileValidationPipe implements PipeTransform {
private readonly allowedMimeTypes = [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf'
];
private readonly maxFileSize = 5 * 1024 * 1024; // 5MB
transform(file: Express.Multer.File): Express.Multer.File {
if (!file) {
throw new BadRequestException('Keine Datei hochgeladen');
}
if (!this.allowedMimeTypes.includes(file.mimetype)) {
throw new BadRequestException(
`Dateityp nicht erlaubt. Erlaubte Typen: ${this.allowedMimeTypes.join(', ')}`
);
}
if (file.size > this.maxFileSize) {
throw new BadRequestException(
`Datei zu groß. Maximum: ${this.maxFileSize / 1024 / 1024}MB`
);
}
return file;
}
}@Controller('upload')
export class UploadController {
@Post('validated')
@UseInterceptors(FileInterceptor('file'))
uploadWithValidation(
@UploadedFile(FileValidationPipe) file: Express.Multer.File
) {
return { message: 'Datei erfolgreich hochgeladen', file: file.filename };
}
}Für produktive Anwendungen ist oft Cloud Storage die bessere Wahl als lokaler Speicher.
npm install aws-sdkimport { Injectable } from '@nestjs/common';
import { S3 } from 'aws-sdk';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class S3Service {
private s3: S3;
constructor(private configService: ConfigService) {
this.s3 = new S3({
accessKeyId: this.configService.get('AWS_ACCESS_KEY_ID'),
secretAccessKey: this.configService.get('AWS_SECRET_ACCESS_KEY'),
region: this.configService.get('AWS_REGION'),
});
}
async uploadFile(file: Express.Multer.File, key: string): Promise<string> {
const uploadParams = {
Bucket: this.configService.get('AWS_S3_BUCKET_NAME'),
Key: key,
Body: file.buffer,
ContentType: file.mimetype,
ACL: 'public-read',
};
const result = await this.s3.upload(uploadParams).promise();
return result.Location;
}
async deleteFile(key: string): Promise<void> {
await this.s3.deleteObject({
Bucket: this.configService.get('AWS_S3_BUCKET_NAME'),
Key: key,
}).promise();
}
}npm install @google-cloud/storageimport { Injectable } from '@nestjs/common';
import { Storage } from '@google-cloud/storage';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class GcsService {
private storage: Storage;
private bucketName: string;
constructor(private configService: ConfigService) {
this.storage = new Storage({
projectId: this.configService.get('GCP_PROJECT_ID'),
keyFilename: this.configService.get('GCP_KEY_FILE'),
});
this.bucketName = this.configService.get('GCS_BUCKET_NAME');
}
async uploadFile(file: Express.Multer.File, fileName: string): Promise<string> {
const bucket = this.storage.bucket(this.bucketName);
const fileUpload = bucket.file(fileName);
const stream = fileUpload.createWriteStream({
metadata: {
contentType: file.mimetype,
},
});
return new Promise((resolve, reject) => {
stream.on('error', reject);
stream.on('finish', () => {
resolve(`gs://${this.bucketName}/${fileName}`);
});
stream.end(file.buffer);
});
}
}Für große Dateien ist Streaming eine effiziente Lösung.
import { Controller, Get, Param, Res, StreamableFile } from '@nestjs/common';
import { Response } from 'express';
import { createReadStream, existsSync } from 'fs';
import { join } from 'path';
@Controller('files')
export class FileController {
@Get('download/:filename')
downloadFile(@Param('filename') filename: string, @Res({ passthrough: true }) res: Response) {
const filePath = join(process.cwd(), 'uploads', filename);
if (!existsSync(filePath)) {
throw new NotFoundException('Datei nicht gefunden');
}
const file = createReadStream(filePath);
res.set({
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename="${filename}"`,
});
return new StreamableFile(file);
}
@Get('view/:filename')
viewFile(@Param('filename') filename: string): StreamableFile {
const filePath = join(process.cwd(), 'uploads', filename);
const file = createReadStream(filePath);
return new StreamableFile(file);
}
}import { Injectable } from '@nestjs/common';
import { createWriteStream } from 'fs';
import { join } from 'path';
@Injectable()
export class StreamUploadService {
async uploadLargeFile(filename: string, stream: NodeJS.ReadableStream): Promise<void> {
const uploadPath = join(process.cwd(), 'uploads', filename);
const writeStream = createWriteStream(uploadPath);
return new Promise((resolve, reject) => {
stream.pipe(writeStream);
writeStream.on('finish', resolve);
writeStream.on('error', reject);
});
}
}Für die Verarbeitung von Bildern kann Sharp verwendet werden.
npm install sharp
npm install @types/sharp --save-devimport { Injectable } from '@nestjs/common';
import * as sharp from 'sharp';
import { join } from 'path';
@Injectable()
export class ImageProcessingService {
async resizeImage(
file: Express.Multer.File,
width: number,
height: number
): Promise<Buffer> {
return await sharp(file.buffer)
.resize(width, height, {
fit: 'cover',
position: 'center',
})
.jpeg({ quality: 80 })
.toBuffer();
}
async createThumbnail(file: Express.Multer.File): Promise<Buffer> {
return await sharp(file.buffer)
.resize(200, 200)
.jpeg({ quality: 70 })
.toBuffer();
}
async optimizeImage(file: Express.Multer.File): Promise<Buffer> {
const metadata = await sharp(file.buffer).metadata();
if (metadata.format === 'jpeg') {
return await sharp(file.buffer)
.jpeg({ quality: 85, progressive: true })
.toBuffer();
} else if (metadata.format === 'png') {
return await sharp(file.buffer)
.png({ compressionLevel: 8 })
.toBuffer();
}
return file.buffer;
}
}@Controller('images')
export class ImageController {
constructor(private imageProcessingService: ImageProcessingService) {}
@Post('upload')
@UseInterceptors(FileInterceptor('image'))
async uploadImage(@UploadedFile() file: Express.Multer.File) {
const optimized = await this.imageProcessingService.optimizeImage(file);
const thumbnail = await this.imageProcessingService.createThumbnail(file);
// Speichern der verarbeiteten Bilder
// ... Implementierung abhängig vom Storage-System
return {
message: 'Bild erfolgreich hochgeladen und verarbeitet',
originalSize: file.size,
optimizedSize: optimized.length,
};
}
}Sicherheit: Validieren Sie immer Dateitypen und -größen. Speichern Sie hochgeladene Dateien außerhalb des Web-Root-Verzeichnisses.
Performance: Nutzen Sie Streaming für große Dateien und implementieren Sie asynchrone Verarbeitung für zeitaufwändige Operationen.
Skalierbarkeit: Verwenden Sie Cloud Storage für produktive Anwendungen anstatt lokaler Dateispeicherung.
Monitoring: Überwachen Sie Upload-Volumes und Speicherverbrauch, um Ressourcen-Limits rechtzeitig zu erkennen.