WisarWisar
Dasturlash kitobi/8-QISM — NestJS22 daqiqa

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). Swaggeravtomatik 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):

ts
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)

ts
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). Yoki MulterModule.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):

ts
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. Lekin FileTypeValidator MIME'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)

ts
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'shiladi

Custom file validatorFileValidator (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)

ts
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.fieldname bilan ajratiladi). NestJS Multer'ning single/array/fields/any 5.11-bob ekvivalenti. AnyFilesInterceptor cheklovsiz maydon qabul qiladi — limits.files bilan cheklang (DoS — 14).

2.6. MulterModule (global sozlash)

ts
// 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)

ts
// 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:

ts
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). @Header yoki @Res({ passthrough: true }) (8.1: 2.9) bilan Content-Type/Content-Disposition (attachment — yuklab olish; inline — brauzerda ochish — 5.11: 2.13). S3 fayl uchun ham: GetObjectCommand javobining Body (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)

text
   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):

ts
// 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) createDocument setup("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)

ts
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)

json
// nest-cli.json — Swagger plugin (avtomatik @ApiProperty)
{
  "compilerOptions": {
    "plugins": ["@nestjs/swagger"]                   // DTO'lardan avtomatik
  }
}

Swagger plugin (@nestjs/swagger CLI plugin) — DTO maydonlarini avtomatik Swagger'ga qo'shadi (@ApiProperty har 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

ts
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: FilterDto bo'lsa DTO'ning @ApiPropertyOptional'laridan query avtomatik chiqadi); alohida yoki maxsus tavsif — qo'lda @ApiQuery.

2.13. Fayl Swagger'da (@ApiConsumes, @ApiBody)

ts
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)

text
  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)

text
  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

ts
// 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)

ts
@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)

ts
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)

ts
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)

ts
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)

ts
// 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)

ts
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)

ts
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)

ts
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)

ts
// 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)

ts
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)

ts
// 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

text
 hajm/tur tekshirilmaydi (DoS, zararli — 14)
 ParseFilePipe (MaxFileSize + FileType + magic bytes — 2.3, 2.4)

2) Faqat MIME tekshirish (spoof)

text
 FileTypeValidator (MIME spoof — 5.11: 2.8)
 magic bytes custom validator (2.4)

3) Faylning o'zini DB'ga

text
 buffer DB'ga (5.11: 2.12)
 S3/disk + DB'ga URL

4) Swagger qo'lda hujjat

text
 alohida hujjat yozish (eskirib qoladi — 5.23: 2.12)
 DTO + @ApiProperty (avtomatik, sinxron — 2.10)

5) Fayl mantig'i controller'da

text
 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)

  1. Avatar yuklash: FileInterceptor + ParseFilePipe (hajm/tur — Misol 1, 2.1, 2.3).
  2. Multer sozlash: disk, noyob nom (Misol 2, 2.2).
  3. Magic bytes: custom validator (spoof — Misol 3, 2.4, 14).
  4. Ko'p fayl: FilesInterceptor (galereya — Misol 9, 2.5).
  5. S3 (bonus): memoryStorage + S3 service (Misol 4, 2.7).
  6. Static: ServeStaticModule (lokal ko'rsatish — Misol 9, 2.8). 6-b. Faylni qaytarish: StreamableFile (yuklab olish/stream — Misol 9-b, 2.7-b).
  7. Swagger setup: DocumentBuilder + addBearerAuth (Misol 5, 2.9).
  8. DTO Swagger: @ApiProperty + validatsiya (bir manba — Misol 6, 2.10).
  9. Controller Swagger: @ApiTags/@ApiOperation/@ApiResponse (Misol 7, 2.12).
  10. 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!
8.8-bob: File upload, Swagger — Wisar