File Upload va Static Files
Muammodan boshlaylik
Foydalanuvchi profil rasmi yukladi. Fayl qayerga saqlanadi? Qanday formatlar ruxsat? Hajmi chegaralanganmi? Xaker .php yoki .exe fayl yuklasa nima bo'ladi?
Multer — bu savollarning barchasiga NestJS ichida javob beradi.
Loyiha tuzilishi
src/
├── app.module.ts
├── main.ts
│
├── upload/
│ ├── upload.module.ts
│ ├── upload.controller.ts
│ ├── upload.service.ts
│ └── multer.config.ts ← Multer sozlamalari
│
└── users/
├── users.controller.ts ← avatar yuklash endpoint
└── ...
uploads/ ← yuklangan fayllar (gitignore da)
avatars/
documents/
temp/
O'rnatish
npm install @nestjs/platform-express multer
npm install -D @types/multer
@nestjs/platform-express — NestJS ning Express adapteri, Multer ichida keladi.
Multer konfiguratsiya
// src/upload/multer.config.ts
import { diskStorage } from 'multer';
import { extname } from 'path';
import { BadRequestException } from '@nestjs/common';
import { v4 as uuid } from 'uuid'; // npm install uuid @types/uuid
// Ruxsat etilgan rasm formatlari
const IMAGE_TYPES = ['.jpg', '.jpeg', '.png', '.webp', '.gif'];
const DOC_TYPES = ['.pdf', '.doc', '.docx', '.xls', '.xlsx'];
// Umumiy diskka saqlash factory
export function diskStorageConfig(destination: string) {
return diskStorage({
destination: `./uploads/${destination}`, // papka
filename: (req, file, cb) => {
// Asl nom o'rniga uuid — noyob, xavfsiz
const ext = extname(file.originalname).toLowerCase();
cb(null, `${uuid()}${ext}`);
},
});
}
// Fayl turini tekshirish factory
export function fileFilter(allowedTypes: string[]) {
return (req: any, file: Express.Multer.File, cb: any) => {
const ext = extname(file.originalname).toLowerCase();
if (!allowedTypes.includes(ext)) {
return cb(
new BadRequestException(
`Faqat ${allowedTypes.join(', ')} formatlar ruxsat`
),
false,
);
}
cb(null, true);
};
}
// Tayyor konfiguratsiyalar
export const avatarUploadConfig = {
storage: diskStorageConfig('avatars'),
fileFilter: fileFilter(IMAGE_TYPES),
limits: { fileSize: 2 * 1024 * 1024 }, // 2MB
};
export const documentUploadConfig = {
storage: diskStorageConfig('documents'),
fileFilter: fileFilter(DOC_TYPES),
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
};
Upload Module
// src/upload/upload.module.ts
import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
import { UploadController } from './upload.controller';
import { UploadService } from './upload.service';
@Module({
controllers: [UploadController],
providers: [UploadService],
exports: [UploadService],
})
export class UploadModule {}
Upload Service — fayl boshqarish
// src/upload/upload.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { unlink, access } from 'fs/promises';
import { join } from 'path';
@Injectable()
export class UploadService {
// Fayl URL sini tuzish
getFileUrl(filename: string, folder: string): string {
return `/uploads/${folder}/${filename}`;
}
// Eski faylni o'chirish (avatar yangilananda)
async deleteFile(filepath: string): Promise<void> {
const fullPath = join(process.cwd(), filepath);
try {
await access(fullPath); // fayl bormi?
await unlink(fullPath); // o'chirish
} catch {
// Fayl yo'q — muammo emas, davom etish
}
}
// Fayl ma'lumotlarini olish
getFileInfo(file: Express.Multer.File) {
return {
filename: file.filename,
originalName: file.originalname,
size: file.size,
mimetype: file.mimetype,
url: this.getFileUrl(file.filename, this.getFolderByMime(file.mimetype)),
};
}
private getFolderByMime(mimetype: string): string {
if (mimetype.startsWith('image/')) return 'avatars';
return 'documents';
}
}
Upload Controller — endpointlar
// src/upload/upload.controller.ts
import {
Controller, Post, UseInterceptors,
UploadedFile, UploadedFiles, UseGuards,
BadRequestException,
} from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { UploadService } from './upload.service';
import { avatarUploadConfig, documentUploadConfig } from './multer.config';
@Controller('upload')
@UseGuards(JwtAuthGuard)
export class UploadController {
constructor(private uploadService: UploadService) {}
// Bitta rasm yuklash
// POST /upload/avatar
@Post('avatar')
@UseInterceptors(FileInterceptor('file', avatarUploadConfig))
uploadAvatar(@UploadedFile() file: Express.Multer.File) {
if (!file) throw new BadRequestException('Fayl yuklanmadi');
return this.uploadService.getFileInfo(file);
}
// Ko'p fayl yuklash (maksimal 5 ta)
// POST /upload/documents
@Post('documents')
@UseInterceptors(FilesInterceptor('files', 5, documentUploadConfig))
uploadDocuments(@UploadedFiles() files: Express.Multer.File[]) {
if (!files?.length) throw new BadRequestException('Fayllar yuklanmadi');
return files.map(f => this.uploadService.getFileInfo(f));
}
}
FileInterceptor('file', config) — bitta fayl. 'file' — form-data dagi maydon nomi.
FilesInterceptor('files', 5, config) — ko'p fayl, maksimal 5 ta.
Avatar yuklash — Users controller bilan birlashtirish
// src/users/users.controller.ts
import { Controller, Post, UseInterceptors, UploadedFile, Req, UseGuards } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { avatarUploadConfig } from '../upload/multer.config';
import { UploadService } from '../upload/upload.service';
import { UsersService } from './users.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
@Controller('users')
@UseGuards(JwtAuthGuard)
export class UsersController {
constructor(
private usersService: UsersService,
private uploadService: UploadService,
) {}
// POST /users/avatar — profil rasmi yangilash
@Post('avatar')
@UseInterceptors(FileInterceptor('file', avatarUploadConfig))
async updateAvatar(
@Req() req,
@UploadedFile() file: Express.Multer.File,
) {
if (!file) throw new BadRequestException('Fayl yuklanmadi');
// Eski avatar URL ni olib, o'chiramiz
const currentUser = await this.usersService.findById(req.user.id);
if (currentUser.photo) {
await this.uploadService.deleteFile(currentUser.photo);
}
// Yangi URL ni saqlaymiz
const url = this.uploadService.getFileUrl(file.filename, 'avatars');
await this.usersService.update(req.user.id, { photo: url });
return { photo: url };
}
}
Static fayllarni ochish
Yuklangan fayllarni brauzerdan ko'rish uchun uploads/ papkasini public qilish:
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// /uploads/avatars/uuid.jpg → ./uploads/avatars/uuid.jpg
app.useStaticAssets(join(__dirname, '..', 'uploads'), {
prefix: '/uploads',
});
await app.listen(3000);
}
Endi http://localhost:3000/uploads/avatars/uuid.jpg orqali ko'rinadi.
Nima uchun NestExpressApplication? useStaticAssets — Express spesifik metod. Generic INestApplication da mavjud emas.
.gitignore va uploads/ papkasi
# .gitignore
uploads/
# Lekin papka tuzilishi git da bo'lsin
# uploads/avatars/.gitkeep
# uploads/documents/.gitkeep
# Papkalarni yarating
mkdir -p uploads/avatars uploads/documents uploads/temp
touch uploads/avatars/.gitkeep uploads/documents/.gitkeep
Tez-tez uchraydigan xatolar
uploads/ papkasi yo'q — Multer xato beradi
// ❌ Papka yo'q bo'lsa MulterError
destination: './uploads/avatars'
// ✅ Papkani oldin yarating yoki kod bilan tekshiring
import { mkdirSync, existsSync } from 'fs';
const dest = './uploads/avatars';
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
Fayl turi MIME bilan emas, kengaytma bilan tekshiriladi
// ❌ MIME soxtalashtirish mumkin — .jpg faylni image/png deb yuborsa
if (file.mimetype !== 'image/jpeg') ...
// ✅ Kengaytmani ham tekshirish
const ext = extname(file.originalname).toLowerCase();
if (!['.jpg', '.jpeg', '.png'].includes(ext)) ...
Asl fayl nomi saqlansa — path traversal xavfi
// ❌ Xavfli — "../../etc/passwd" kabi nom yuborilishi mumkin
filename: (req, file, cb) => cb(null, file.originalname)
// ✅ UUID bilan noyob va xavfsiz nom
filename: (req, file, cb) => cb(null, `${uuid()}${extname(file.originalname)}`)
Xulosa
File Upload — Multer + NestJS interceptor. Konfiguratsiyani alohida faylda saqlash, service da fayl boshqarish, controller da faqat endpoint — toza arxitektura.
Eslab qoling:
diskStorageConfig(folder)— papka va nom factoryfileFilter(types)— ruxsat etilgan formatlar factorylimits.fileSize— hajm chegarasi (baytda)uuid()— xavfsiz fayl nomiapp.useStaticAssets(path, { prefix })— statik fayllaruploads/papkasi git da emas,.gitkeepbilan tuzilish saqlash
Keyingi dars
31-dars: Amazon S3 / Cloud File Storage