8.8-bob: File upload, Swagger
8-QISM — NestJS (chuqur) · 8-mavzu
1. Kirish va motivatsiya
RBAC'ni 8.7-bob bildik. Endi ikki amaliy, har real API'da kerak bo'ladigan mavzuni — NestJS'da fayl yuklash (FileInterceptor) va Swagger (avtomatik API hujjat) — o'rganamiz. Ikkalasi 5-QISMda mustaqil ko'rilgan edi (5.11: Multer, 5.23: Swagger); endi NestJS'da — dekorator va interceptor bilan integratsiyalashgan, tashkillangan holda. Bu — fayl (avatar, hujjat) qabul qilish va API'ni hujjatlash — professional API'ning ikki muhim qismi.
Fayl yuklash NestJS'da FileInterceptor orqali (8.6: interceptor + Multer — 5.11). NestJS Multer'ni 5.11-bob o'rab oladi: @UseInterceptors(FileInterceptor("rasm")) + @UploadedFile() — fayl olinadi. ParseFilePipe (8.5: pipe) — fayl tur/hajmini tekshiradi (5.11: 2.7, 2.8). Cloud (S3 — 5.11: 2.11) ham integratsiyalashadi. Bu — 5.11'ning NestJS versiyasi (deklarativ, toza).
Swagger — NestJS'da eng kuchli integratsiya: @nestjs/swagger DTO'lardan 8.5-bob va dekoratorlardan avtomatik OpenAPI hujjat yaratadi 5.23-bob. DTO bir vaqtda: tur (7) + validatsiya 8.5-bob + hujjat (Swagger) — bitta manba (DRY). @ApiTags, @ApiProperty, @ApiOperation — deklarativ hujjat. Va /api sahifasi — interaktiv ("Try it out" — 5.23). Bu bob: FileInterceptor, ParseFilePipe, S3, Swagger setup, DTO hujjat, va fayl Swagger — chuqur.
O'xshatish: FileInterceptor — pochta qabulxonasi (5.11: pochta kuryeri — NestJS'da tashkillangan): posilkani (fayl) qabul qiladi, tekshiradi (ParseFilePipe — hajm/tur), omborga joylaydi (disk/S3). Swagger — avtomatik tuzilgan foydalanuvchi qo'llanmasi (5.23: instruksiya): siz kod yozasiz (DTO, dekorator), Swagger undan qo'llanma (interaktiv API hujjat) avtomatik yasaydi — qo'lda yozmaysiz, kod bilan sinxron.
Nega muhim?
- Fayl yuklash — avatar, hujjat — deyarli har ilova 5.11-bob.
- Swagger — frontend/jamoa API'ni biladi (interaktiv — 5.23).
- DTO bir manba — tur + validatsiya + hujjat (DRY).
- NestJS integratsiya — deklarativ, toza (Express'dan farqli).
2. Nazariya — chuqur tushuntirish
2.1. FileInterceptor (bitta fayl — 5.11)
FileInterceptor — bitta faylni qabul qiladi (Multer ustida — 8.6 interceptor — docs):
import { Controller, Post, UseInterceptors, UploadedFile } from "@nestjs/common";
import { FileInterceptor } from "@nestjs/platform-express";
@Controller("upload")
export class UploadController {
@Post("avatar")
@UseInterceptors(FileInterceptor("rasm")) // "rasm" — form maydon nomi (5.11: 2.5)
yukla(@UploadedFile() file: Express.Multer.File) { // fayl (5.11: 2.6)
return { nom: file.originalname, hajm: file.size };
}
}FileInterceptor —
@UseInterceptors(FileInterceptor("field"))+@UploadedFile()— Multer'ni 5.11-bob NestJS'da.file: Express.Multer.File— fayl (originalname, size, buffer/path — 5.11: 2.6). Bu — 5.11'ning deklarativ versiyasi (interceptor + dekorator).
2.2. Multer sozlash (storage, limits — 5.11)
import { diskStorage } from "multer";
@Post("avatar")
@UseInterceptors(
FileInterceptor("rasm", { // sozlama (5.11: 2.3, 2.7)
storage: diskStorage({ // disk (5.11: 2.4)
destination: "./uploads",
filename: (req, file, cb) => {
cb(null, `${crypto.randomUUID()}${extname(file.originalname)}`); // noyob (5.11: 2.9)
},
}),
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB (DoS — 5.11: 2.7, 14)
fileFilter: (req, file, cb) => { // tur (5.11: 2.8)
cb(null, file.mimetype.startsWith("image/"));
},
}),
)
yukla(@UploadedFile() file: Express.Multer.File) {}Multer sozlamasi (5.11: 2.3-2.8) — FileInterceptor ikkinchi argumentida:
storage(disk/memory),limits(hajm — DoS — 14),fileFilter(tur). YokiMulterModule.register(global — 2.6). Bu — 5.11 sozlamasining NestJS joylashuvi.
2.3. ParseFilePipe (validatsiya — zamonaviy)
ParseFilePipe — fayl tur/hajmini deklarativ tekshiradi (8.5: pipe — docs):
import { ParseFilePipe, MaxFileSizeValidator, FileTypeValidator } from "@nestjs/common";
@Post("avatar")
@UseInterceptors(FileInterceptor("rasm"))
yukla(
@UploadedFile(
new ParseFilePipe({
validators: [
new MaxFileSizeValidator({ maxSize: 5 * 1024 * 1024 }), // 5MB (14)
new FileTypeValidator({ fileType: /(jpg|jpeg|png|webp)$/ }), // tur
],
}),
)
file: Express.Multer.File,
) {}ParseFilePipe — fayl validatsiyasini pipe sifatida 8.5-bob:
MaxFileSizeValidator(hajm),FileTypeValidator(MIME). fileFilter o'rniga zamonaviy, deklarativ. LekinFileTypeValidatorMIME'ni tekshiradi (spoof qilinishi mumkin — 5.11: 2.8); ishonchli — magic bytes (custom validator — 2.4, 5.11: Misol 4).
2.4. Custom file validator (magic bytes — 5.11: 2.8, 14)
import { FileValidator } from "@nestjs/common";
import { fileTypeFromBuffer } from "file-type";
// Magic bytes validator (MIME spoof himoyasi — 5.11: 2.8, 14)
export class FileSignatureValidator extends FileValidator {
async isValid(file: Express.Multer.File): Promise<boolean> {
const turi = await fileTypeFromBuffer(file.buffer); // haqiqiy tur (5.11: 2.8)
return turi ? ["image/jpeg", "image/png"].includes(turi.mime) : false;
}
buildErrorMessage(): string {
return "Fayl haqiqatan rasm emas";
}
}
// ParseFilePipe validators'ga qo'shiladiCustom file validator —
FileValidator(isValid, buildErrorMessage). Magic bytes (5.11: 2.8) — MIME spoof himoyasi (haqiqiy tur — fayl baytlaridan — 14). MemoryStorage kerak (buffer). Bu — fayl xavfsizligining muhim qismi (5.11: 2.14).
2.5. FilesInterceptor (ko'p fayl — 5.11: 2.5)
import {
FilesInterceptor,
FileFieldsInterceptor,
AnyFilesInterceptor,
} from "@nestjs/platform-express";
// Ko'p fayl (bir maydon)
@Post("gallery")
@UseInterceptors(FilesInterceptor("rasmlar", 8)) // max 8 (5.11: 2.5)
ko'pYukla(@UploadedFiles() files: Express.Multer.File[]) {
return files.map((f) => f.filename);
}
// Turli maydonlar (har maydon alohida nom + limit)
@Post("profile")
@UseInterceptors(FileFieldsInterceptor([
{ name: "avatar", maxCount: 1 },
{ name: "hujjatlar", maxCount: 5 },
]))
profil(
@UploadedFiles() files: { avatar?: Express.Multer.File[]; hujjatlar?: Express.Multer.File[] },
) {
return { avatar: files.avatar?.[0]?.filename, hujjat: files.hujjatlar?.length };
}
// Har qanday maydon nomi (nom oldindan noma'lum bo'lsa)
@Post("aralash")
@UseInterceptors(AnyFilesInterceptor()) // istalgan field nomi (5.11: 2.5)
aralash(@UploadedFiles() files: Express.Multer.File[]) {
return files.map((f) => ({ maydon: f.fieldname, nom: f.originalname }));
}FilesInterceptor (ko'p fayl, bir maydon —
@UploadedFiles()); FileFieldsInterceptor (turli maydonlar — avatar + galereya — 5.11: 2.5); AnyFilesInterceptor (istalgan maydon nomi — field oldindan noma'lum bo'lsa,f.fieldnamebilan ajratiladi). NestJS Multer'ning single/array/fields/any 5.11-bob ekvivalenti. AnyFilesInterceptor cheklovsiz maydon qabul qiladi —limits.filesbilan cheklang (DoS — 14).
2.6. MulterModule (global sozlash)
// app.module.ts — global Multer (5.8: config bilan)
import { MulterModule } from "@nestjs/platform-express";
@Module({
imports: [
MulterModule.registerAsync({ // async (ConfigService — 8.3: 2.4)
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
dest: config.get("UPLOAD_DIR"),
limits: { fileSize: 5 * 1024 * 1024 },
}),
}),
],
})
export class AppModule {}MulterModule — global Multer sozlash (
register/registerAsync— 8.3: 2.4). Har FileInterceptor'da takrorlamasdan, markaziy. ConfigService bilan (env — 5.8). Yoki har interceptor'da alohida 2.2-bob.
2.7. Cloud upload (S3 — 5.11: 2.11)
// MemoryStorage + S3 (5.11: 2.11)
@Injectable()
export class UploadService {
private s3 = new S3Client({ region: this.config.get("AWS_REGION") }); // (5.11)
async s3gaYukla(file: Express.Multer.File): Promise<string> {
const kalit = `uploads/${crypto.randomUUID()}${extname(file.originalname)}`;
await this.s3.send(new PutObjectCommand({
Bucket: this.config.get("S3_BUCKET"),
Key: kalit,
Body: file.buffer, // memoryStorage (5.11: 2.3)
ContentType: file.mimetype,
}));
return `https://${bucket}.s3.amazonaws.com/${kalit}`; // URL (DB'ga — 5.11: 2.12)
}
}
// Controller: FileInterceptor (memoryStorage) service.s3gaYukla(file)S3 upload (5.11: 2.11): memoryStorage (buffer) S3Client (PutObjectCommand). URL qaytariladi (DB'ga faqat URL — 5.11: 2.12). Service'da (controller yupqa — 8.1: 2.7). Production'da cloud (5.11: 2.11). multer-s3 ham 5.11-bob.
2.7-b. Faylni qaytarish — StreamableFile va streaming (5.11: 2.13)
Fayl yuklashni ko'rdik; teskarisi — faylni mijozga qaytarish (download/stream). Butun faylni xotiraga o'qib res.send qilish katta fayllarda RAM'ni to'ldiradi (DoS xavfi — 14). To'g'ri yo'l — stream (bo'lak-bo'lak): NestJS'da StreamableFile:
import { Controller, Get, Param, StreamableFile, Res, Header } from "@nestjs/common";
import { createReadStream } from "fs";
import { join } from "path";
import type { Response } from "express";
@Controller("files")
export class FilesController {
// 1) Sodda: StreamableFile qaytarish (NestJS o'zi stream qiladi)
@Get(":nom")
@Header("Content-Type", "application/octet-stream")
@Header("Content-Disposition", 'attachment; filename="hujjat.pdf"') // yuklab olish (5.11: 2.13)
yuklab(@Param("nom") nom: string): StreamableFile {
const stream = createReadStream(join("./uploads", nom)); // bo'lak-bo'lak (RAM tejaladi — 14)
return new StreamableFile(stream); // NestJS pipe qiladi
}
// 2) Passthrough: header'ni qo'lda boshqarish ({ passthrough: true })
@Get(":nom/inline")
koʻrsat(
@Param("nom") nom: string,
@Res({ passthrough: true }) res: Response, // passthrough — NestJS javobni yopmaydi (8.1: 2.9)
): StreamableFile {
res.set({ "Content-Type": "image/png", "Content-Disposition": "inline" });
return new StreamableFile(createReadStream(join("./uploads", nom)));
}
}StreamableFile — faylni stream (bo'lak-bo'lak) qaytaradi:
new StreamableFile(readStream | Buffer). Butun faylni RAM'ga o'qimaydi — katta fayl uchun xavfsiz (14).@Headeryoki@Res({ passthrough: true })(8.1: 2.9) bilanContent-Type/Content-Disposition(attachment — yuklab olish; inline — brauzerda ochish — 5.11: 2.13). S3 fayl uchun ham:GetObjectCommandjavobiningBody(stream)new StreamableFile(Body as Readable).@Res({ passthrough: true })— passthrough bo'lmasa NestJS interceptor/serializatsiya ishlamaydi 8.6-bob.
2.8. Fayl xavfsizligi (5.11: 2.14 — NestJS'da)
Hajm limit (ParseFilePipe MaxFileSize — DoS — 14)
Tur (FileType + magic bytes custom validator — spoof — 2.4)
Noyob nom (crypto — path traversal — 5.11: 2.9)
DB'ga faqat URL/yo'l (faylning o'zi emas — 5.11: 2.12)
Cloud (S3 — production — 2.7)
Static ko'rsatish (ServeStaticModule — lokal)Fayl xavfsizligi (5.11: 2.14) NestJS'da: ParseFilePipe (hajm/tur) + custom magic bytes 2.4-bob. Lokal fayl ko'rsatish —
ServeStaticModule. DB'ga URL (5.11: 2.12). Bu — 5.11 xavfsizligining NestJS amaliyoti.
2.9. Swagger nima va sozlash (5.23)
Swagger — NestJS'da @nestjs/swagger bilan avtomatik API hujjat (5.23 — docs):
// main.ts
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
const config = new DocumentBuilder() // (5.23: 2.6)
.setTitle("Mana API")
.setDescription("E-commerce backend")
.setVersion("1.0")
.addBearerAuth() // JWT auth (5.23: 2.10)
.addTag("users")
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup("api", app, document); // /api sahifasi (5.23: 2.3)Swagger setup 5.23-bob:
DocumentBuilder(sozlama — title/version/auth)createDocumentsetup("api")(sahifa). NestJS avtomatik controller/DTO'lardan hujjat yasaydi (5.23: 2.4 — qo'lda izoh emas!)./api— interaktiv ("Try it out"). Bu — 5.23'ning eng kuchli NestJS integratsiyasi.
2.10. DTO Swagger hujjat (@ApiProperty — bir manba)
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
export class CreateUserDto {
@ApiProperty({ example: "Ali", description: "Foydalanuvchi ismi" }) // Swagger
@IsString() @MinLength(2) // validatsiya (8.5)
ism: string;
@ApiProperty({ example: "ali@a.uz" })
@IsEmail()
email: string;
@ApiPropertyOptional({ minimum: 18 }) // ixtiyoriy
@IsOptional() @IsInt() @Min(18)
yosh?: number;
}DTO — bir manba (DRY): bitta DTO'da tur (7) + validatsiya (
@IsEmail— 8.5) + hujjat (@ApiProperty— Swagger).@ApiProperty(majburiy),@ApiPropertyOptional(ixtiyoriy). NestJS DTO'dan avtomatik Swagger schema. Bu — 5.23: 2.12 (sinxronlik) ning NestJS'dagi tabiiy yechimi.
2.11. Swagger plugin (avtomatik — kamroq dekorator)
// nest-cli.json — Swagger plugin (avtomatik @ApiProperty)
{
"compilerOptions": {
"plugins": ["@nestjs/swagger"] // DTO'lardan avtomatik
}
}Swagger plugin (
@nestjs/swaggerCLI plugin) — DTO maydonlarini avtomatik Swagger'ga qo'shadi (@ApiPropertyhar maydonga yozmasdan — type/optional'ni TS'dan oladi). Kamroq dekorator, ko'proq avtomatlashtirish. Faqat misol/maxsus uchun qo'lda@ApiProperty.
2.12. Controller Swagger dekoratorlari
import {
ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiParam, ApiQuery,
} from "@nestjs/swagger";
@ApiTags("users") // guruh (5.23: 2.7)
@ApiBearerAuth() // auth kerak (5.23: 2.10)
@Controller("users")
export class UsersController {
@Get()
@ApiOperation({ summary: "Ro'yxat" })
@ApiQuery({ name: "page", required: false, type: Number, example: 1 }) // query param
@ApiQuery({ name: "search", required: false, description: "Ism bo'yicha qidiruv" })
hammasi(@Query("page") page?: number, @Query("search") search?: string) {}
@Get(":id")
@ApiParam({ name: "id", type: Number, description: "Foydalanuvchi ID" }) // path param
bitta(@Param("id", ParseIntPipe) id: number) {}
@Post()
@ApiOperation({ summary: "Yangi foydalanuvchi" }) // tavsif
@ApiResponse({ status: 201, description: "Yaratildi", type: UserResponseDto })
@ApiResponse({ status: 409, description: "Email band" }) // (5.23: 2.11)
yarat(@Body() dto: CreateUserDto) {}
}Controller dekoratorlari 5.23-bob:
@ApiTags(guruh),@ApiOperation(tavsif),@ApiResponse(javoblar — status/type — 5.23: 2.11),@ApiBearerAuth(auth),@ApiParam(path parametr —:id),@ApiQuery(query parametr —?page=&search=— required/type/example). Bular — hujjatni boyitadi. Ko'pi avtomatik (DTO'dan — masalan@Query() dto: FilterDtobo'lsa DTO'ning@ApiPropertyOptional'laridan query avtomatik chiqadi); alohida yoki maxsus tavsif — qo'lda@ApiQuery.
2.13. Fayl Swagger'da (@ApiConsumes, @ApiBody)
import { ApiConsumes, ApiBody } from "@nestjs/swagger";
@Post("avatar")
@ApiConsumes("multipart/form-data") // fayl turi (5.11: 2.1)
@ApiBody({
schema: {
type: "object",
properties: { rasm: { type: "string", format: "binary" } }, // fayl
},
})
@UseInterceptors(FileInterceptor("rasm"))
yukla(@UploadedFile() file: Express.Multer.File) {}Fayl Swagger'da:
@ApiConsumes("multipart/form-data")(5.11: 2.1) +@ApiBody(fayl maydoni —format: "binary"). Swagger UI'da fayl yuklash tugmasi chiqadi ("Try it out" — fayl tanlash). DTO bilan ham (@ApiProperty({ type: "string", format: "binary" })).
2.14. Swagger'dan client generatsiya (bonus)
Swagger (OpenAPI) hujjatidan:
- Frontend client (openapi-generator, orval) — tipli API funksiyalar
- Postman import (kollektsiya)
- Test/validatsiya
DTO Swagger frontend client (full-stack tur sinxronligi)OpenAPI standart (5.23: 2.14) — Swagger hujjatdan frontend client (tipli) generatsiya (orval, openapi-generator). Backend DTO Swagger frontend tur (full-stack sinxron). Bu — NestJS + frontend (11) izchilligi.
2.15. Best practices (14)
FILE:
ParseFilePipe (hajm/tur — 14) + magic bytes custom validator (2.3, 2.4)
Noyob nom; DB'ga URL; S3 production (5.11: 2.9, 2.12, 2.7)
Service'da fayl mantig'i (controller yupqa — 2.7)
SWAGGER:
DTO bir manba (@ApiProperty + validatsiya — 2.10)
Swagger plugin (avtomatik — 2.11)
@ApiTags/@ApiResponse (boyitish — 2.12)
addBearerAuth (auth — 2.9); production'da himoya (5.23: 2.13)3. Sintaksis — tez ma'lumotnoma
// File (2.1-2.5, 2.7-b)
@UseInterceptors(FileInterceptor("rasm"))
yukla(@UploadedFile(new ParseFilePipe({ validators: [...] })) file: Express.Multer.File) {}
@UseInterceptors(FilesInterceptor("rasmlar", 8)) yukla(@UploadedFiles() files) {}
@UseInterceptors(FileFieldsInterceptor([{ name: "avatar", maxCount: 1 }])) // turli maydon
@UseInterceptors(AnyFilesInterceptor()) // istalgan field nomi
return new StreamableFile(createReadStream(yol)); // fayl qaytarish/stream (2.7-b)
// Swagger (2.9, 2.10, 2.12)
const config = new DocumentBuilder().setTitle("API").addBearerAuth().build();
SwaggerModule.setup("api", app, SwaggerModule.createDocument(app, config));
@ApiTags("users") @ApiProperty({ example }) @ApiOperation({ summary })
@ApiParam({ name: "id" }) @ApiQuery({ name: "page", required: false }) // param/query (2.12)
@ApiConsumes("multipart/form-data") // fayl (2.13)4. Batafsil kod namunalari
Misol 1 — Avatar yuklash (ParseFilePipe — 2.1, 2.3)
@Controller("users")
export class UsersController {
constructor(private uploadService: UploadService) {}
@Post(":id/avatar")
@UseInterceptors(FileInterceptor("avatar")) // (2.1)
async yuklaAvatar(
@Param("id", ParseIntPipe) id: number,
@UploadedFile(
new ParseFilePipe({ // validatsiya (2.3)
validators: [
new MaxFileSizeValidator({ maxSize: 2 * 1024 * 1024 }), // 2MB (14)
new FileTypeValidator({ fileType: /(jpg|jpeg|png|webp)$/ }),
],
}),
)
file: Express.Multer.File,
) {
const url = await this.uploadService.saqla(file); // service (2.7)
return { avatar: url };
}
}Misol 2 — Multer sozlash (disk, noyob nom — 2.2)
import { diskStorage } from "multer";
import { extname } from "path";
import { randomUUID } from "crypto";
export const multerOptions = {
storage: diskStorage({
destination: "./uploads/avatars",
filename: (req, file, cb) => {
const noyob = randomUUID(); // (5.11: 2.9)
cb(null, `${noyob}${extname(file.originalname)}`); // path traversal himoyasi (14)
},
}),
limits: { fileSize: 2 * 1024 * 1024 }, // 2MB (14)
fileFilter: (req, file, cb) => {
const ok = /(jpg|jpeg|png|webp)$/.test(file.mimetype);
cb(ok ? null : new BadRequestException("Faqat rasm"), ok); // (5.11: 2.8)
},
};
// Controller
@UseInterceptors(FileInterceptor("avatar", multerOptions))
yukla(@UploadedFile() file: Express.Multer.File) {}Misol 3 — Custom magic bytes validator (2.4, 14)
import { FileValidator } from "@nestjs/common";
import { fileTypeFromBuffer } from "file-type";
export class MagicBytesValidator extends FileValidator {
constructor(private allowed: string[]) { super({}); }
async isValid(file: Express.Multer.File): Promise<boolean> {
if (!file?.buffer) return false; // memoryStorage kerak
const turi = await fileTypeFromBuffer(file.buffer); // haqiqiy tur (5.11: 2.8)
return turi ? this.allowed.includes(turi.mime) : false;
}
buildErrorMessage(): string {
return `Fayl haqiqatan ${this.allowed.join("/")} emas`;
}
}
// Ishlatish (memoryStorage bilan)
@UploadedFile(
new ParseFilePipe({
validators: [
new MaxFileSizeValidator({ maxSize: 5e6 }),
new MagicBytesValidator(["image/jpeg", "image/png"]), // spoof himoyasi (14)
],
}),
)
file: Express.Multer.File,Misol 4 — S3 upload service (2.7)
import { Injectable } from "@nestjs/common";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { ConfigService } from "@nestjs/config";
@Injectable()
export class UploadService {
private s3: S3Client;
constructor(private config: ConfigService) {
this.s3 = new S3Client({ region: config.get("AWS_REGION") }); // (5.8)
}
async s3gaYukla(file: Express.Multer.File, papka = "uploads"): Promise<string> {
const kalit = `${papka}/${randomUUID()}${extname(file.originalname)}`; // noyob (14)
await this.s3.send(new PutObjectCommand({
Bucket: this.config.get("S3_BUCKET"),
Key: kalit,
Body: file.buffer, // memoryStorage (5.11: 2.3)
ContentType: file.mimetype,
}));
return `https://${this.config.get("S3_BUCKET")}.s3.amazonaws.com/${kalit}`; // URL (5.11: 2.12)
}
}
// Controller (memoryStorage + S3)
@Post("upload")
@UseInterceptors(FileInterceptor("file")) // default memoryStorage (yoki sozla)
async yukla(@UploadedFile() file: Express.Multer.File) {
const url = await this.uploadService.s3gaYukla(file);
return { url }; // DB'ga URL (5.11: 2.12)
}Misol 5 — Swagger setup (to'liq — 2.9)
// main.ts
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })); // (8.5)
const config = new DocumentBuilder()
.setTitle("Mana Do'kon API")
.setDescription("E-commerce backend — to'liq REST API")
.setVersion("1.0")
.addBearerAuth() // JWT (2.9, 8.9)
.addTag("auth", "Autentifikatsiya")
.addTag("users", "Foydalanuvchilar")
.addTag("products", "Mahsulotlar")
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup("api/docs", app, document, {
swaggerOptions: { persistAuthorization: true }, // token saqlanadi (qulay)
});
await app.listen(3000);
console.log("Swagger: http://localhost:3000/api/docs");
}
bootstrap();Misol 6 — DTO Swagger (bir manba — 2.10)
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsString, IsEmail, MinLength, IsOptional, IsEnum } from "class-validator";
export class CreateUserDto {
@ApiProperty({ example: "Ali Valiyev", description: "To'liq ism", minLength: 2 })
@IsString() @MinLength(2)
ism: string;
@ApiProperty({ example: "ali@example.uz", format: "email" })
@IsEmail()
email: string;
@ApiProperty({ example: "Parol123!", minLength: 8, writeOnly: true }) // writeOnly — javobda yo'q
@IsString() @MinLength(8)
parol: string;
@ApiPropertyOptional({ enum: ["user", "admin"], default: "user" })
@IsOptional() @IsEnum(["user", "admin"])
rol?: string;
}
// Bir DTO: tur (7) + validatsiya 8.5-bob + Swagger hujjat (DRY!)Misol 7 — Controller Swagger (2.12)
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiParam } from "@nestjs/swagger";
@ApiTags("users")
@Controller("users")
export class UsersController {
@Get()
@ApiOperation({ summary: "Barcha foydalanuvchilar", description: "Sahifalangan ro'yxat" })
@ApiResponse({ status: 200, description: "Ro'yxat", type: [UserResponseDto] })
hammasi(@Query() filter: FilterDto) {}
@Get(":id")
@ApiParam({ name: "id", description: "Foydalanuvchi ID" })
@ApiResponse({ status: 200, type: UserResponseDto })
@ApiResponse({ status: 404, description: "Topilmadi" }) // (2.12)
bitta(@Param("id", ParseIntPipe) id: number) {}
@Post()
@ApiBearerAuth() // auth kerak (2.9)
@ApiOperation({ summary: "Yangi foydalanuvchi" })
@ApiResponse({ status: 201, type: UserResponseDto })
@ApiResponse({ status: 409, description: "Email band" })
yarat(@Body() dto: CreateUserDto) {}
}Misol 8 — Fayl Swagger DTO bilan (2.13)
import { ApiProperty } from "@nestjs/swagger";
// Fayl uchun DTO (Swagger — 2.13)
export class FileUploadDto {
@ApiProperty({ type: "string", format: "binary" }) // fayl maydoni
rasm: any;
@ApiProperty({ example: "Profil rasmi", required: false })
izoh?: string;
}
@Controller("upload")
@ApiTags("upload")
export class UploadController {
@Post("avatar")
@ApiConsumes("multipart/form-data") // (2.13, 5.11: 2.1)
@ApiBody({ type: FileUploadDto }) // Swagger fayl tugmasi
@UseInterceptors(FileInterceptor("rasm"))
yukla(
@UploadedFile(new ParseFilePipe({ validators: [new MaxFileSizeValidator({ maxSize: 2e6 })] }))
file: Express.Multer.File,
) {
return { nom: file.originalname };
}
}
// Swagger UI'da fayl tanlash + yuklash tugmasi (interaktiv — 5.23: 2.3)Misol 9 — Ko'p fayl + static ko'rsatish (2.5, 2.8)
// Ko'p fayl (galereya — 2.5)
@Post("gallery")
@UseInterceptors(FilesInterceptor("rasmlar", 10, multerOptions))
ko'pYukla(@UploadedFiles() files: Express.Multer.File[]) {
return { yuklandi: files.length, fayllar: files.map((f) => f.filename) };
}
// Static ko'rsatish (lokal fayl — 2.8)
// app.module.ts
import { ServeStaticModule } from "@nestjs/serve-static";
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, "..", "uploads"), // /uploads/... URL (5.11: 2.13)
serveRoot: "/uploads",
}),
],
})
export class AppModule {}Misol 9-b — Faylni qaytarish (StreamableFile — 2.7-b)
import { Controller, Get, Param, StreamableFile, Res, NotFoundException } from "@nestjs/common";
import { createReadStream, existsSync, statSync } from "fs";
import { join } from "path";
import type { Response } from "express";
import { GetObjectCommand } from "@aws-sdk/client-s3";
import type { Readable } from "stream";
@ApiTags("files")
@Controller("files")
export class FilesController {
constructor(private config: ConfigService, private s3: S3Client) {}
// Lokal fayl — yuklab olish (attachment)
@Get("local/:nom")
@ApiOperation({ summary: "Lokal faylni yuklab olish" })
@ApiParam({ name: "nom", description: "Fayl nomi" })
yuklab(
@Param("nom") nom: string,
@Res({ passthrough: true }) res: Response, // passthrough (8.1: 2.9)
): StreamableFile {
const yol = join("./uploads", nom);
if (!existsSync(yol)) throw new NotFoundException("Fayl topilmadi"); // (8.4)
res.set({
"Content-Type": "application/octet-stream",
"Content-Disposition": `attachment; filename="${nom}"`, // yuklab olish (5.11: 2.13)
"Content-Length": statSync(yol).size, // progress bar (mijozda)
});
return new StreamableFile(createReadStream(yol)); // stream (RAM tejash — 14)
}
// S3 fayl — proxy stream (bucket private bo'lsa)
@Get("s3/:kalit")
async s3dan(
@Param("kalit") kalit: string,
@Res({ passthrough: true }) res: Response,
): Promise<StreamableFile> {
const obj = await this.s3.send(new GetObjectCommand({
Bucket: this.config.get("S3_BUCKET"),
Key: kalit,
}));
res.set({ "Content-Type": obj.ContentType ?? "application/octet-stream" });
return new StreamableFile(obj.Body as Readable); // S3 stream mijoz (2.7)
}
}StreamableFile butun faylni xotiraga o'qimaydi — bo'lak-bo'lak stream qiladi (5GB video ham RAM'siz — 14). Private S3 uchun to'g'ridan-to'g'ri URL o'rniga backend proxy (yuqoridagi
s3dan) yoki presigned URL (5.11: 2.11) — ikkalasi ham amaliyotda uchraydi.
Misol 10 — To'liq upload modul (service bilan — 2.7)
// upload.service.ts
@Injectable()
export class UploadService {
constructor(
private config: ConfigService,
@InjectRepository(User) private userRepo: Repository<User>, // (8.3)
) {}
async avatarYangila(userId: number, file: Express.Multer.File): Promise<string> {
const url = await this.s3gaYukla(file, "avatars"); // (Misol 4)
await this.userRepo.update(userId, { avatar: url }); // DB'ga URL (5.11: 2.12)
return url;
}
private async s3gaYukla(file, papka) { /* Misol 4 */ return ""; }
}
// Controller — yupqa (2.7)
@Controller("users")
export class UsersController {
constructor(private uploadService: UploadService) {}
@Patch(":id/avatar")
@UseGuards(AuthGuard) // auth (8.7)
@ApiConsumes("multipart/form-data")
@UseInterceptors(FileInterceptor("avatar"))
async avatar(
@Param("id", ParseIntPipe) id: number,
@UploadedFile(new ParseFilePipe({ validators: [new MaxFileSizeValidator({ maxSize: 2e6 })] }))
file: Express.Multer.File,
) {
const url = await this.uploadService.avatarYangila(id, file);
return { success: true, avatar: url };
}
}5. To'g'ri va noto'g'ri holatlar
1) Fayl validatsiyasisiz
hajm/tur tekshirilmaydi (DoS, zararli — 14)
ParseFilePipe (MaxFileSize + FileType + magic bytes — 2.3, 2.4)2) Faqat MIME tekshirish (spoof)
FileTypeValidator (MIME spoof — 5.11: 2.8)
magic bytes custom validator (2.4)3) Faylning o'zini DB'ga
buffer DB'ga (5.11: 2.12)
S3/disk + DB'ga URL4) Swagger qo'lda hujjat
alohida hujjat yozish (eskirib qoladi — 5.23: 2.12)
DTO + @ApiProperty (avtomatik, sinxron — 2.10)5) Fayl mantig'i controller'da
S3/qayta ishlash controller'da (qalin — 2.7)
service'da (controller yupqa — 8.1: 2.7)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Cannot read 'buffer' (magic bytes)
Sababi: diskStorage (buffer yo'q) — magic bytes memoryStorage kerak 2.4-bob. Yechimi: memoryStorage; yoki diskdan o'qish.
Xato 2 — Fayl undefined
Sababi: form maydon nomi mos emas, yoki multipart emas (5.11: 6-xato). Yechimi: FileInterceptor("nom") form maydoniga mos; @ApiConsumes.
Xato 3 — ParseFilePipe ishlamaydi (fayl yo'q)
Sababi: fayl ixtiyoriy bo'lsa fileIsRequired: false. Yechimi: new ParseFilePipe({ fileIsRequired: false, validators }).
Xato 4 — Swagger sahifasi bo'sh
Sababi: createDocument/setup noto'g'ri, yoki controller import. Yechimi: setup to'g'ri; controller AppModule'da.
Xato 5 — DTO Swagger'da ko'rinmaydi
Sababi: @ApiProperty yo'q (plugin'siz — 2.11). Yechimi: @ApiProperty; yoki Swagger plugin.
Xato 6 — Fayl Swagger'da yuklab bo'lmaydi
Sababi: @ApiConsumes/@ApiBody yo'q 2.13-bob. Yechimi: @ApiConsumes("multipart/form-data") + @ApiBody.
Xato 7 — StreamableFile'da ERR_HTTP_HEADERS_SENT yoki javob bo'sh
Sababi: @Res() passthrough'siz ishlatilgan — NestJS javobni yopmaydi/ikki marta yozadi (8.1: 2.9, 2.7-b). Yechimi: @Res({ passthrough: true }); yoki @Res() ishlatmay faqat StreamableFile qaytaring va header'ni @Header bilan bering.
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- File upload 5.11-bob: Multer — NestJS'da FileInterceptor; StreamableFile (fayl qaytarish — 2.7-b).
- Swagger 5.23-bob: OpenAPI — NestJS avtomatik.
- Interceptor/Pipe (8.6, 8.5): FileInterceptor, ParseFilePipe.
- DTO 8.5-bob: @ApiProperty + validatsiya.
- DB 8.3-bob: fayl URL saqlash.
- Cloud (5.11: 2.11, 10.6): S3.
- Auth 8.9-bob: addBearerAuth, @UseGuards.
- Config 8.14-bob: AWS/upload sozlama.
- Frontend (11): Swagger'dan client.
8. Eng yaxshi amaliyotlar (best practices)
- ParseFilePipe (hajm/tur — 14) + magic bytes custom validator (spoof — 2.3, 2.4).
- Noyob nom; DB'ga URL; S3 production (5.11: 2.9, 2.12, 2.7).
- Katta fayl qaytarish — StreamableFile (stream, RAM tejash — 2.7-b, 14).
- Fayl mantig'i service'da (controller yupqa — 2.7).
- DTO bir manba (@ApiProperty + validatsiya — DRY — 2.10).
- Swagger plugin (avtomatik — kamroq dekorator — 2.11).
- @ApiTags/@ApiResponse (boyitish — 2.12).
- addBearerAuth (auth — 2.9); production'da Swagger himoya (5.23: 2.13).
- Fayl Swagger (@ApiConsumes + @ApiBody — 2.13).
- ServeStaticModule (lokal fayl ko'rsatish — 2.8).
- Swagger client (full-stack sinxron — 2.14).
9. Amaliy loyiha: "Fayl Yuklash + Swagger API"
NestJS fayl va hujjatni mustahkamlash.
Maqsad
Fayl yuklash (validatsiya, S3) va to'liq Swagger hujjatli API qurish.
Talablar (requirements)
- Avatar yuklash: FileInterceptor + ParseFilePipe (hajm/tur — Misol 1, 2.1, 2.3).
- Multer sozlash: disk, noyob nom (Misol 2, 2.2).
- Magic bytes: custom validator (spoof — Misol 3, 2.4, 14).
- Ko'p fayl: FilesInterceptor (galereya — Misol 9, 2.5).
- S3 (bonus): memoryStorage + S3 service (Misol 4, 2.7).
- Static: ServeStaticModule (lokal ko'rsatish — Misol 9, 2.8). 6-b. Faylni qaytarish: StreamableFile (yuklab olish/stream — Misol 9-b, 2.7-b).
- Swagger setup: DocumentBuilder + addBearerAuth (Misol 5, 2.9).
- DTO Swagger: @ApiProperty + validatsiya (bir manba — Misol 6, 2.10).
- Controller Swagger: @ApiTags/@ApiOperation/@ApiResponse (Misol 7, 2.12).
- Fayl Swagger: @ApiConsumes + @ApiBody (Misol 8, 2.13).
Maslahatlar (hint)
- ParseFilePipe (hajm/tur — 2.3, 1-xato).
- Magic bytes memoryStorage (2.4, 1-xato).
- DB'ga URL (5.11: 2.12, 3-holat).
- DTO @ApiProperty (avtomatik — 2.10, 4-xato).
- Fayl Swagger: @ApiConsumes (2.13, 6-xato).
- Service'da fayl mantig'i (2.7, 5-holat).
"Tayyor" mezonlari (acceptance criteria)
- Avatar yuklash (validatsiya).
- Multer (noyob nom, limit).
- Magic bytes (spoof himoyasi).
- Ko'p fayl.
- (Bonus) S3.
- Static ko'rsatish.
- Faylni qaytarish (StreamableFile).
- Swagger setup (auth).
- DTO Swagger (bir manba).
- Controller Swagger.
- Fayl Swagger'da yuklanadi.
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda NestJS'da fayl yuklash va Swagger'ni o'rgandik:
- File upload: FileInterceptor (Multer — 2.1, 2.2); ParseFilePipe (validatsiya — 2.3); magic bytes (spoof — 2.4, 14); FilesInterceptor/FileFieldsInterceptor/AnyFilesInterceptor (ko'p — 2.5); S3 (cloud — 2.7); StreamableFile (fayl qaytarish/stream — 2.7-b); xavfsizlik 2.8-bob.
- Swagger: setup (DocumentBuilder — 2.9); DTO bir manba (@ApiProperty + validatsiya — 2.10); plugin (avtomatik — 2.11); controller dekoratorlar 2.12-bob.
- Fayl Swagger (@ApiConsumes + @ApiBody — 2.13); client generatsiya 2.14-bob.
Keyingi bob — 8.9-bob: Autentifikatsiya — JWT, Passport, cookie, refresh token. Fayl/Swagger'ni bildik; endi NestJS'ning eng muhim, eng ko'p so'raladigan mavzusiga — autentifikatsiya ga o'tamiz: JWT 5.15-bob, Passport (NestJS auth strategiyasi), cookie, refresh token 5.16-bob. Guards 8.6-bob va RBAC 8.7-bob bilan birga — to'liq auth tizimi. Bu — har NestJS ilovaning yuragi.
Foydalanilgan rasmiy/ishonchli manbalar
- docs.nestjs.com/techniques/file-upload (FileInterceptor, FilesInterceptor, AnyFilesInterceptor, ParseFilePipe, validators)
- docs.nestjs.com/techniques/streaming-files (StreamableFile, @Res passthrough)
- docs.nestjs.com/openapi/introduction (Swagger, DocumentBuilder, @ApiProperty, @ApiTags)
- github.com/nestjs/swagger; Medium — NestJS file upload + S3 2026
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!