8.21-bob: Hujjat generatsiya — PDF, Excel, CSV
8-QISM — NestJS (chuqur) · 21-mavzu · Amaliy real mavzu
1. Kirish va motivatsiya
Endi yana bir har loyihada uchraydigan real mavzu — hujjat generatsiya (PDF, Excel, CSV). Mijoz buyurtma qilgach chek kerak (PDF); admin hisobot yuklab olmoqchi (Excel); buxgalter ma'lumotni eksport qiladi (CSV); yoki katta ma'lumot import qilinadi (CSV/Excel yuklash). Bu — backend'ning kundalik vazifasi: ma'lumotni inson o'qiy oladigan/boshqa tizimga o'tkaziladigan hujjatga aylantirish. 5.11'da fayl yuklashni ko'rdik; endi fayl yaratish va o'qish (parse) ni o'rganamiz.
Uch format, uch maqsad: PDF — chiroyli, chop etiladigan hujjat (chek, hisob-faktura, shartnoma, sertifikat); Excel (.xlsx) — tahrirlanadigan jadval (hisobot, ma'lumot tahlili, formula); CSV — oddiy, universal jadval (tizimlararo ma'lumot almashinuvi, ommaviy import/export). Har birining o'z kutubxonasi va yondashuvi bor, va eng muhimi — katta ma'lumot bilan ishlash (streaming — xotira to'lib ketmasligi uchun).
Bu bob: PDF generatsiya (PDFKit — dasturiy, Puppeteer — HTMLPDF — qachon qaysi), chek/hisob-faktura, Excel (ExcelJS — yaratish, formula, stil), CSV (yaratish va parse), import (CSV/Excel o'qish, validatsiya), katta ma'lumot streaming (xotira muammosi), va fonda generatsiya (navbat — og'ir hisobot). Bu bob 5.11 (fayl), 8.22 (navbat), 8.19 (chek) bilan bog'liq. Hujjat generatsiya — har biznesda kerak (chek, hisobot, eksport).
O'xshatish: hujjat generatsiya — bosmaxona. Sizda raqamli ma'lumot bor (DB), lekin mijoz/admin uni qo'lda ushlanadigan, chop etiladigan shaklda xohlaydi. PDF — chiroyli bosilgan chek/sertifikat (formati qat'iy, bezatilgan). Excel — tahrirlanadigan jadval (raqamlar, formulalar — buxgalter ishlay oladi). CSV — oddiy ro'yxat (har qanday dastur o'qiy oladi — universal). Bosmaxona katta buyurtmani (10000 chek) bir vaqtda emas, konveyer (streaming) bilan chiqaradi (aks holda joy yetmaydi — xotira). Backend — raqamli bosmaxona.
Nega muhim?
- Har biznesda — chek, hisob-faktura, hisobot, sertifikat.
- Eksport/import — Excel/CSV (buxgalteriya, tahlil, ko'chirish).
- Mijoz talabi — "PDF yuklab olish", "Excel hisobot".
- Katta ma'lumot — streaming (xotira boshqaruvi — muhim ko'nikma).
2. Nazariya — chuqur tushuntirish
2.1. Uch format — qachon qaysi
┌────────┬──────────────────────────────────────────────────┐
│ PDF │ Chiroyli, qat'iy format, chop etiladigan. │
│ │ Chek, hisob-faktura, shartnoma, sertifikat. │
│ │ Tahrirlanmaydigan (yakuniy hujjat). │
├────────┼──────────────────────────────────────────────────┤
│ Excel │ Tahrirlanadigan jadval, formula, stil, grafik. │
│ (.xlsx)│ Hisobot, ma'lumot tahlili (buxgalter ishlaydi). │
├────────┼──────────────────────────────────────────────────┤
│ CSV │ Oddiy matn jadval (vergul bilan). Universal. │
│ │ Tizimlararo ko'chirish, ommaviy import/export. │
│ │ Yengil, tez (katta ma'lumot — streaming). │
└────────┴──────────────────────────────────────────────────┘Format tanlovi: PDF — chop etiladigan, qat'iy yakuniy hujjat (chek, shartnoma); Excel — tahrirlanadigan, formulali jadval (hisobot — buxgalter ishlaydi); CSV — oddiy, universal, yengil (tizimlararo, katta import/export). Maqsadga qarab: mijozga chek PDF; admin hisobot Excel; ma'lumot ko'chirish CSV. Noto'g'ri format — noqulaylik (chek Excel'da g'alati).
2.2. PDF: PDFKit vs Puppeteer (qachon qaysi)
PDFKit (dasturiy — kod bilan chizish):
Yengil, tez, kam resurs; aniq nazorat (koordinata, chiziq)
Murakkab dizayn qiyin (qo'lda joylashtirish)
Oddiy chek, sertifikat, hisobot (dasturiy)
Puppeteer (HTML PDF — brauzer):
HTML/CSS bilan dizayn (chiroyli, oson — veb kabi)
Og'ir (headless Chrome — resurs, sekinroq)
Murakkab dizayn (hisob-faktura, kataloq — HTML shablon)PDFKit vs Puppeteer: PDFKit (dasturiy — kod bilan chizish: matn, chiziq, koordinata) — yengil, tez, oddiy hujjat (chek, sertifikat). Puppeteer (HTMLPDF — headless Chrome) — HTML/CSS bilan dizayn (chiroyli, oson — veb tajribasi), lekin og'ir (Chrome — resurs). Murakkab/chiroyli dizayn Puppeteer (HTML shablon — 8.10 Handlebars kabi); oddiy/ko'p PDFKit (yengil). Hybrid ham mumkin.
2.3. PDFKit (dasturiy PDF)
import PDFDocument from "pdfkit";
function chekYarat(buyurtma: any): PDFDocument {
const doc = new PDFDocument({ size: "A4", margin: 50 });
doc.fontSize(20).text("CHEK", { align: "center" }); // sarlavha
doc.moveDown();
doc.fontSize(12).text(`Buyurtma: #${buyurtma.id}`);
doc.text(`Sana: ${buyurtma.sana}`);
doc.moveDown();
buyurtma.items.forEach((i: any) => { // mahsulotlar
doc.text(`${i.nom} — ${i.miqdor} x ${i.narx} = ${i.miqdor * i.narx} so'm`);
});
doc.moveDown().fontSize(14).text(`Jami: ${buyurtma.jami} so'm`, { align: "right" });
doc.end(); // yakunlash
return doc; // stream (2.7)
}PDFKit — dasturiy PDF (kod bilan):
doc.text(),fontSize(),moveDown(),image(), chiziq. Koordinata/oqim nazorati.doc.end()— yakunlash.doc— stream (response'ga to'g'ridan pipe — 2.7, xotirada to'liq saqlamasdan). O'zbek harflari uchun shrift (registerFont— Unicode shrift kerak, default Helvetica ʻ ni qo'llamasligi mumkin). Oddiy chek, sertifikat uchun ideal.
2.3a. O'zbek shrift (UTF-8 / font embed) — batafsil
// MUAMMO: PDFKit default shrifti (Helvetica — Afm) faqat lotin-1 (Latin-1).
// O'zbek "o'g'", "ʻ" (tutuq), kirill — kvadrat (□□□) chiqadi.
// YECHIM: TrueType (.ttf) Unicode shrift EMBED qilish (PDF ichiga joylash).
import PDFDocument from "pdfkit";
import { join } from "path";
const doc = new PDFDocument();
// 1) Shriftni ro'yxatdan o'tkazish (nom bilan)
doc.registerFont("uz", join(process.cwd(), "assets/fonts/DejaVuSans.ttf"));
doc.registerFont("uz-bold", join(process.cwd(), "assets/fonts/DejaVuSans-Bold.ttf"));
// 2) Faol shriftni tanlash — endi barcha text() shu shrift bilan
doc.font("uz").fontSize(12).text("Bo'ronbekning to'lovi — 1 500 000 so'm");
doc.font("uz-bold").text("JAMI"); // qalin variantFont embed (o'zbek — 2.3): default Afm shriftlar (Helvetica/Times) faqat Latin-1 o'zbek harflari (
o',g', tutuq belgisiʻ, kirill) buziladi (kvadrat). Yechim: TrueType (.ttf) Unicode shriftni loyihaga qo'shib (assets/fonts/),registerFont(nom, yo'l)bilan embed qilish. Bepul variantlar: DejaVu Sans, Noto Sans, Roboto (barchasi kirill + kengaytirilgan lotin qamrab oladi). Shrift PDF ichiga joylashadi — natijada hujjat har kompyuterda bir xil ko'rinadi (shrift o'rnatilmagan bo'lsa ham). Qalin/kursiv uchun alohida.ttf(bir shrift avtomatik qalin bo'lmaydi). Bu — o'zbek hujjatida majburiy qadam.
2.3b. pdfmake (declarativ PDF — docDefinition)
import PdfPrinter from "pdfmake";
// Shriftlar (o'zbek — 2.3a): pdfmake ham TTF embed qiladi
const printer = new PdfPrinter({
Roboto: {
normal: "assets/fonts/Roboto-Regular.ttf",
bold: "assets/fonts/Roboto-Medium.ttf",
italics: "assets/fonts/Roboto-Italic.ttf",
bolditalics: "assets/fonts/Roboto-MediumItalic.ttf",
},
});
function chekDocDefinition(buyurtma: any) {
return { // DEKLARATIV — obyekt (chizmaymiz, tavsiflaymiz)
content: [
{ text: "MANA DO'KON", style: "sarlavha" },
{ text: `Chek: #${buyurtma.id}`, margin: [0, 10, 0, 4] },
{
table: { // JADVAL — deklarativ (koordinata emas)
headerRows: 1,
widths: ["*", "auto", "auto", "auto"],
body: [
["Mahsulot", "Miqdor", "Narx", "Jami"], // sarlavha qatori
...buyurtma.items.map((i: any) => [
i.nom, i.miqdor, i.narx, i.miqdor * i.narx,
]),
],
},
},
{ text: `JAMI: ${buyurtma.jami} so'm`, style: "jami", alignment: "right" },
],
styles: {
sarlavha: { fontSize: 22, bold: true, alignment: "center" },
jami: { fontSize: 14, bold: true, margin: [0, 10, 0, 0] },
},
defaultStyle: { font: "Roboto" }, // o'zbek shrift
};
}
async function pdfmakeYarat(buyurtma: any): Promise<Buffer> {
const doc = printer.createPdfKitDocument(chekDocDefinition(buyurtma));
const chunks: Buffer[] = [];
doc.on("data", (c) => chunks.push(c));
doc.end();
return new Promise((resolve) => doc.on("end", () => resolve(Buffer.concat(chunks))));
}pdfmake — deklarativ PDF (PDFKit ustiga qurilgan): PDF'ni koordinata bilan chizmaysiz, balki obyekt (
docDefinition) bilan tavsiflaysiz —content,table,columns,styles. Jadval (chek, hisob-faktura) PDFKit'dagi qo'lda joylashtirishga qaraganda ancha oson (table.body— massiv). Avtomatik sahifa bo'linishi,pageBreak, footer/header. Shrift ham TTF embed (o'zbek — 2.3a). PDFKit vs pdfmake: erkin grafika/koordinata (sertifikat, ramka) PDFKit; strukturaviy jadvalli hujjat (chek, faktura) pdfmake (deklarativ — o'qish oson). Ikkalasi ham yengil (Puppeteer'siz — brauzer kerak emas).
2.4. Puppeteer (HTML PDF)
import puppeteer from "puppeteer";
async function htmlPdfYarat(html: string): Promise<Buffer> {
const browser = await puppeteer.launch({ headless: true, args: ["--no-sandbox"] });
const page = await browser.newPage();
await page.setContent(html, { waitUntil: "networkidle0" }); // HTML yuklash
const pdf = await page.pdf({ format: "A4", printBackground: true }); // PDF
await browser.close(); // resurs bo'shatish (MUHIM)
return pdf;
}
// HTML — Handlebars shablon 8.10-bob bilan: chiroyli hisob-fakturaPuppeteer — HTMLPDF (headless Chrome). HTML/CSS bilan dizayn (Handlebars shablon — 8.10 kabi)
page.pdf(). Chiroyli, murakkab dizayn oson (veb bilimingiz ishlaydi).browser.close()majburiy (har generatsiyada Chrome ochilsa, yopilmasa — xotira sizishi). Production'da browser pool (har safar Chrome ochmaslik — sekin). Og'ir, lekin chiroyli (hisob-faktura, kataloq).
2.5. Excel (ExcelJS)
import * as ExcelJS from "exceljs";
async function hisobotYarat(buyurtmalar: any[]): Promise<Buffer> {
const wb = new ExcelJS.Workbook();
const ws = wb.addWorksheet("Buyurtmalar");
ws.columns = [ // ustunlar (sarlavha + kenglik)
{ header: "ID", key: "id", width: 10 },
{ header: "Mijoz", key: "mijoz", width: 25 },
{ header: "Summa", key: "summa", width: 15 },
{ header: "Sana", key: "sana", width: 20 },
];
ws.getRow(1).font = { bold: true }; // sarlavha qalin (stil)
buyurtmalar.forEach((b) => ws.addRow(b)); // qatorlar
ws.addRow({ mijoz: "JAMI:", summa: { formula: `SUM(C2:C${buyurtmalar.length + 1})` } }); // formula!
return (await wb.xlsx.writeBuffer()) as Buffer;
}Excel (ExcelJS) — to'liq .xlsx:
addWorksheet,columns(sarlavha/kenglik),addRow, stil (font, rang, border), formula ({ formula: "SUM(...)" }— Excel ichida hisoblanadi). Buxgalter tahrirlay/hisoblay oladi.writeBuffer(yoki stream — katta ma'lumot — 2.7). Hisobot, moliyaviy jadval uchun.xlsxkutubxonasi ham (yengilroq, lekin stil/formula cheklangan).
2.6. CSV (yaratish va parse)
// CSV YARATISH (oddiy — qo'lda yoki kutubxona)
function csvYarat(data: any[]): string {
const sarlavha = Object.keys(data[0]).join(",");
const qatorlar = data.map((row) =>
Object.values(row).map((v) => `"${String(v).replace(/"/g, '""')}"`).join(","), // escape!
);
return [sarlavha, ...qatorlar].join("\n");
}
// CSV PARSE (o'qish — import)
import { parse } from "csv-parse";
async function csvOqi(buffer: Buffer): Promise<any[]> {
return new Promise((resolve, reject) => {
parse(buffer, { columns: true, trim: true }, (err, records) => {
if (err) reject(err); else resolve(records); // har qator obyekt
});
});
}CSV — oddiy matn jadval (vergul). Yaratish: sarlavha + qatorlar ( escape — qiymatda vergul/qo'shtirnoq bo'lsa). Parse (import):
csv-parse(columns: true— birinchi qator sarlavha obyekt). CSV nozik (vergul, yangi qator, kodlash — UTF-8 BOM Excel uchun). Universal, yengil, katta ma'lumotga ideal (streaming — 2.7). Tizimlararo ko'chirish.
2.6a. fast-csv (parse va format — stream)
import * as fastcsv from "fast-csv";
import { Readable } from "stream";
// PARSE (import) — stream, katta faylga (xotira to'lmaydi — 2.7)
function fastCsvOqi(buffer: Buffer): Promise<any[]> {
return new Promise((resolve, reject) => {
const rows: any[] = [];
Readable.from(buffer)
.pipe(fastcsv.parse({ headers: true, trim: true })) // birinchi qator sarlavha obyekt
.on("error", reject)
.on("data", (row) => rows.push(row)) // qator-qator (streamda)
.on("end", () => resolve(rows));
});
}
// FORMAT (eksport) — response'ga to'g'ridan stream (escape avtomatik)
@Get("export/fast.csv")
async fastExport(@Res() res: Response) {
res.setHeader("Content-Type", "text/csv; charset=utf-8");
res.setHeader("Content-Disposition", "attachment; filename=orders.csv");
res.write(""); // UTF-8 BOM (Excel — 2.6)
const csvStream = fastcsv.format({ headers: true }); // vergul/qo'shtirnoq — AVTOMATIK escape
csvStream.pipe(res); // to'g'ridan javobga (stream)
const dbStream = this.orderRepo.createQueryBuilder("o").stream(); // DB stream (8.3)
dbStream.on("data", (r) => csvStream.write({ ID: r.o_id, Mijoz: r.o_mijoz, Summa: r.o_summa }));
dbStream.on("end", () => csvStream.end());
}fast-csv — CSV uchun stream-first kutubxona (parse + format bir joyda).
parse({ headers: true })— sarlavhani obyekt kalitiga aylantiradi;format()— escape'ni (vergul, qo'shtirnoq, yangi qator) avtomatik bajaradi (qo'ldareplaceshart emas — 2.6 dagi xatolikka o'rin qolmaydi). Node stream'ga to'g'ridanpipe— katta eksport/import uchun ideal 2.7-bob. Backend'da (fayl/DB stream) eng qulay tanlov. Papa Parse — bir xil maqsad, lekin brauzer (frontend — 11-qism) uchun mashhur; backend'da hamPapa.parse(text, { header: true })ishlaydi (kichik/o'rta fayl uchun), katta stream uchunfast-csvafzal.
2.6b. Papa Parse (CSV parse — sodda)
import Papa from "papaparse";
// Sodda parse (kichik/o'rta fayl — butun matnni bir marta) — frontend'da ham xuddi shu API
function papaOqi(matn: string): any[] {
const natija = Papa.parse(matn, {
header: true, // birinchi qator — kalit
skipEmptyLines: true, // bo'sh qatorlarni tashla
dynamicTyping: true, // "12" 12 (raqamga avtomatik)
});
if (natija.errors.length) {
throw new BadRequestException(`CSV xato: ${natija.errors[0].message} (qator ${natija.errors[0].row})`);
}
return natija.data; // [{ nom: "...", narx: 5000 }, ...]
}Papa Parse — eng mashhur CSV parser (ayniqsa frontend — 11-qism); backend'da ham ishlaydi.
header: true(obyekt),dynamicTyping(avtomatik raqam),skipEmptyLines. Xatolarninatija.errorsda qaytaradi (qator raqami bilan). Papa Parse butun matnni xotiraga oladi (kichik/o'rta fayl — o'nlab ming qator uchun yaxshi); juda katta (yuz mingdan ortiq) fayl uchunfast-csvstreaming (2.6a) afzal. Frontendbackend bir xil API — bilim ko'chadi.
2.7. Streaming (katta ma'lumot — xotira)
// MUAMMO: 100000 qator xotirada to'liq out of memory!
// YECHIM: streaming (qator-qator, xotirada to'planmaydi)
@Get("export")
async export(@Res() res: Response) {
res.setHeader("Content-Type", "text/csv");
res.setHeader("Content-Disposition", "attachment; filename=hisobot.csv");
const stream = this.orderRepo.createQueryBuilder("o").stream(); // DB stream (8.3)
res.write("id,mijoz,summa\n"); // sarlavha
stream.on("data", (row) => res.write(`${row.id},${row.mijoz},${row.summa}\n`)); // qator-qator
stream.on("end", () => res.end()); // tugadi
}Streaming (katta ma'lumot — eng muhim ko'nikma): 100000 qatorni xotiraga to'liq yuklash out of memory (server qulaydi). Yechim: stream — qator-qator o'qib, darrov javobga yozish (xotirada to'planmaydi). DB stream 8.3-bob response stream. ExcelJS streaming writer ham. Katta eksport — har doim stream (yoki sahifalab — 8.13). Bu — production'da kritik (kichik testda sezilmaydi, katta datada qulaydi).
2.7a. StreamableFile (NestJS idiomatik yo'l)
import { StreamableFile } from "@nestjs/common";
import { createReadStream } from "fs";
// USUL A: mavjud fayl (yoki buffer) StreamableFile qaytarish (@Res() kerak emas!)
@Get("reports/orders.xlsx")
@Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@Header("Content-Disposition", 'attachment; filename="buyurtmalar.xlsx"')
async hisobot(): Promise<StreamableFile> {
const buffer = await this.excelHisobot(await this.ordersService.hammasi());
return new StreamableFile(buffer); // NestJS o'zi pipe qiladi
}
// USUL B: disk/S3 dagi katta fayl read stream (xotiraga yuklamasdan)
@Get("files/:id")
faylBer(@Param("id") id: string): StreamableFile {
const fayl = createReadStream(join(process.cwd(), "storage", id));
return new StreamableFile(fayl, {
type: "application/pdf",
disposition: 'attachment; filename="hujjat.pdf"', // metadata konstruktorda ham beriladi
});
}StreamableFile (NestJS idiomatik — 2.7a):
@Res()bilan Expressresni qo'lda boshqarish o'rniga (bu interceptor'larni chetlab o'tadi — 8.6), NestJSStreamableFileqaytaring — framework o'zipipeqiladi.Buffer,ReadableyokiReadStreamqabul qiladi. Sarlavhalar:@Header()dekoratori yoki konstruktorning ikkinchi argumenti (type,disposition). Qachon@Res()? ExcelJS/CSV streaming writer'ida (Misol 4, 5)res.write()ni qadam-baqadam chaqirish kerak o'sha yerda@Res({ passthrough: true })yoki to'g'ridanres; oddiy "buffer/fayl qaytarish"da esaStreamableFileafzal (toza, testlanadigan, interceptor mos). Katta disk fayl —createReadStream(xotiraga to'liq yuklamaydi).
2.8. Import (CSV/Excel o'qish + validatsiya)
@Post("import")
@UseInterceptors(FileInterceptor("file")) // fayl yuklash (8.8)
async import(@UploadedFile() file: Express.Multer.File) {
const records = await this.csvOqi(file.buffer); // parse (2.6)
const natija = { muvaffaqiyat: 0, xato: [] as any[] };
for (const [i, row] of records.entries()) {
const dto = plainToInstance(ImportRowDto, row); // DTO (8.5)
const xatolar = await validate(dto); // validatsiya (8.5)
if (xatolar.length) {
natija.xato.push({ qator: i + 2, xatolar }); // qator raqami (xato hisoboti)
continue;
}
await this.service.yarat(dto);
natija.muvaffaqiyat++;
}
return natija; // muvaffaqiyat + xatolar hisoboti
}Import (CSV/Excel o'qish): fayl yuklash 8.8-bob parse (2.6) har qatorni validatsiya (DTO — 8.5) saqlash. Xato hisoboti (qaysi qatorda nima xato — foydalanuvchiga; qator raqami bilan). Katta import navbatga (2.9, fonda). Tranzaksiya (8.13 — yarmida xato bo'lsa orqaga, yoki "qisman" rejim — talab bo'yicha). Ommaviy ma'lumot kiritish (mahsulot, mijoz ro'yxati).
2.9. Fonda generatsiya (og'ir hisobot — navbat)
// Og'ir hisobot (10000+ qator) fonda (so'rovni bloklamasin — 8.22)
@Post("reports/generate")
async hisobotSora(@Body() dto: ReportDto, @CurrentUser() user) {
const job = await this.reportQueue.add("generate", { ...dto, userId: user.id });
return { jobId: job.id, message: "Hisobot tayyorlanmoqda, tayyor bo'lganda xabar beramiz" };
}
@Processor("reports")
export class ReportProcessor extends WorkerHost {
async process(job: Job) {
const buffer = await this.excelHisobot(job.data); // og'ir generatsiya (2.5)
const url = await this.s3.yukla(buffer, `report-${job.id}.xlsx`); // S3 (5.11)
await this.notify.yubor(job.data.userId, `Hisobot tayyor: ${url}`); // bildirishnoma (8.18)
}
}Fonda generatsiya (og'ir hisobot — navbat — 8.22): katta hisobot (10000+ qator, murakkab) sekin so'rovni bloklamaslik uchun navbatga (BullMQ). Worker fonda yaratadi S3'ga 5.11-bob tayyor bo'lganda bildirishnoma (8.18 — email/push/WebSocket — "hisobotingiz tayyor"). Sinxron (darrov yuklab olish) faqat kichik hujjat uchun (chek). Katta fonda (yaxshi UX).
2.10. Best practices (hujjat)
To'g'ri format (PDF chek, Excel hisobot, CSV ko'chirish — 2.1)
PDFKit (grafika) / pdfmake (jadval-deklarativ) / Puppeteer (chiroyli — 2.2, 2.3b, 2.4)
Puppeteer browser pool + page.close() (xotira — 2.4)
StreamableFile qaytar (@Res() dan afzal — interceptor mos — 2.7a)
Streaming (katta ma'lumot — xotira — 2.7)
fast-csv format() — escape avtomatik (qo'lda replace emas — 2.6a)
Import validatsiya + xato hisoboti (qator raqami — 2.8)
Og'ir generatsiya navbat (fonda — 2.9, 8.22)
O'zbek harflari shrift (Unicode — PDF — 2.3)
CSV escape + UTF-8 BOM (Excel ochishi — 2.6)
Excel formula/stil (buxgalter — 2.5); xavfsizlik (CSV injection — 2.10)Qo'shimcha xavfsizlik: CSV injection — qiymat
=bilan boshlansa (=cmd), Excel uni formula deb bajaradi (xavf). Foydalanuvchi kiritgan ma'lumotni eksportda —=/+/-/@bilan boshlansa, oldiga'qo'yish (14).
3. Sintaksis — tez ma'lumotnoma
// PDFKit 2.3-bob: new PDFDocument() registerFont/font doc.text() doc.end() pipe(res)
// pdfmake (2.3b): printer.createPdfKitDocument(docDefinition) content/table/styles
// Puppeteer 2.4-bob: page.setContent(html) page.pdf() browser.close()
// HandlebarsPDF (2a): Handlebars.compile(hbs)(data) html htmlPdf(html)
// Excel 2.5-bob: wb.addWorksheet() ws.columns/addRow wb.xlsx.writeBuffer()
// Excel stream (Misol 4): stream.xlsx.WorkbookWriter({ stream: res }) row.commit()
// CSV parse 2.6-bob: parse(buffer, { columns: true }) | fast-csv .parse({ headers: true }) | Papa.parse
// CSV format (2.6a): fastcsv.format({ headers: true }).pipe(res) — auto escape
// Stream 2.7-bob: repo.stream() res.write(qator) res.end()
// StreamableFile (2.7a): return new StreamableFile(buffer | readStream) — @Res() shart emas
// Import 2.8-bob: FileInterceptor parse validate saqla4. Batafsil kod namunalari
Misol 1 — PDF chek (PDFKit + stream — 2.3, 2.7)
@Get("orders/:id/receipt")
async chek(@Param("id") id: string, @Res() res: Response) {
const buyurtma = await this.ordersService.bitta(id);
res.setHeader("Content-Type", "application/pdf");
res.setHeader("Content-Disposition", `inline; filename=chek-${id}.pdf`);
const doc = new PDFDocument({ size: "A4", margin: 50 });
doc.pipe(res); // to'g'ridan response'ga (stream — 2.7)
doc.registerFont("uz", "fonts/DejaVuSans.ttf"); // Unicode shrift (o'zbek — 2.3)
doc.font("uz");
doc.fontSize(22).text("MANA DO'KON", { align: "center" });
doc.fontSize(10).text("Chek / Hisob-faktura", { align: "center" });
doc.moveDown();
doc.fontSize(11).text(`Chek: #${buyurtma.id}`);
doc.text(`Sana: ${buyurtma.createdAt.toLocaleDateString("uz")}`);
doc.text(`Mijoz: ${buyurtma.mijoz.ism}`);
doc.moveDown();
// Jadval (mahsulotlar)
buyurtma.items.forEach((i: any) => {
doc.text(`${i.nom}`, 50, doc.y, { continued: true })
.text(`${i.miqdor} x ${i.narx} = ${i.miqdor * i.narx} so'm`, { align: "right" });
});
doc.moveDown();
doc.fontSize(14).text(`JAMI: ${buyurtma.jami} so'm`, { align: "right" });
doc.end(); // stream tugaydi response yopiladi
}Misol 2 — Puppeteer HTML hisob-faktura (2.4)
@Injectable()
export class PdfService {
private browser: puppeteer.Browser; // qayta ishlatish (browser pool)
async onModuleInit() { // (8.1 lifecycle)
this.browser = await puppeteer.launch({ headless: true, args: ["--no-sandbox"] });
}
async onModuleDestroy() { await this.browser?.close(); }
async htmlPdf(html: string): Promise<Buffer> {
const page = await this.browser.newPage(); // pool'dan (har safar launch emas)
try {
await page.setContent(html, { waitUntil: "networkidle0" });
return (await page.pdf({ format: "A4", printBackground: true,
margin: { top: "20mm", bottom: "20mm", left: "15mm", right: "15mm" } })) as Buffer;
} finally {
await page.close(); // sahifa yopiladi (browser qoladi)
}
}
}
// HTML — Handlebars shablon 8.10-bob bilan chiroyli hisob-fakturaMisol 2a — Handlebars shablon HTML PDF (2.4, 8.10)
// invoice.hbs (shablon fayli — assets/templates/invoice.hbs):
//
// <!DOCTYPE html>
// <html><head><meta charset="utf-8"><style>
// body { font-family: 'DejaVu Sans', sans-serif; } /* o'zbek shrift — CSS @font-face ham mumkin */
// table { width: 100%; border-collapse: collapse; }
// th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
// th { background: #4f46e5; color: #fff; }
// .jami { font-weight: bold; text-align: right; font-size: 18px; }
// </style></head><body>
// <h1>Hisob-faktura #{{id}}</h1>
// <p>Mijoz: {{mijoz.ism}} — Sana: {{sana}}</p>
// <table>
// <tr><th>Mahsulot</th><th>Miqdor</th><th>Narx</th><th>Jami</th></tr>
// {{#each items}} {{! sikl — HTML/CSS bilim ishlaydi }}
// <tr><td>{{nom}}</td><td>{{miqdor}}</td><td>{{narx}}</td><td>{{jami}}</td></tr>
// {{/each}}
// </table>
// <p class="jami">JAMI: {{jami}} so'm</p>
// </body></html>
import * as Handlebars from "handlebars";
import { readFileSync } from "fs";
import { join } from "path";
@Injectable()
export class InvoiceService {
// Shablonni bir marta kompilyatsiya qilib keshlash (har so'rovda o'qimaslik)
private shablon = Handlebars.compile(
readFileSync(join(process.cwd(), "assets/templates/invoice.hbs"), "utf-8"),
);
constructor(private readonly pdfService: PdfService) {} // Misol 2 — browser pool
async fakturaPdf(buyurtma: any): Promise<Buffer> {
const html = this.shablon({ // ma'lumot HTML (8.10 — render)
id: buyurtma.id,
mijoz: buyurtma.mijoz,
sana: new Date(buyurtma.createdAt).toLocaleDateString("uz"),
items: buyurtma.items.map((i: any) => ({ ...i, jami: i.miqdor * i.narx })),
jami: buyurtma.jami,
});
return this.pdfService.htmlPdf(html); // HTML PDF (Puppeteer — Misol 2)
}
}
// Handlebars 8.10-bob — chiroyli, tahrirlash oson (dizayner HTML/CSS ni o'zgartiradi, kod tegmaydi)Misol 3 — Excel hisobot (stil + formula — 2.5)
async excelHisobot(buyurtmalar: any[]): Promise<Buffer> {
const wb = new ExcelJS.Workbook();
wb.creator = "Mana Do'kon";
const ws = wb.addWorksheet("Hisobot", {
pageSetup: { paperSize: 9, orientation: "landscape" },
});
// Sarlavha qatori (stil bilan)
ws.columns = [
{ header: "№", key: "n", width: 6 },
{ header: "Buyurtma ID", key: "id", width: 15 },
{ header: "Mijoz", key: "mijoz", width: 30 },
{ header: "Summa (so'm)", key: "summa", width: 18 },
{ header: "Holat", key: "holat", width: 15 },
{ header: "Sana", key: "sana", width: 20 },
];
const sarlavha = ws.getRow(1);
sarlavha.font = { bold: true, color: { argb: "FFFFFFFF" } };
sarlavha.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FF4F46E5" } }; // ko'k fon
sarlavha.alignment = { vertical: "middle", horizontal: "center" };
// Ma'lumot qatorlari
buyurtmalar.forEach((b, i) => {
ws.addRow({ n: i + 1, id: b.id, mijoz: b.mijoz, summa: b.summa, holat: b.holat, sana: b.sana });
});
// Jami (formula — Excel ichida hisoblanadi)
const jamiQator = ws.addRow({ mijoz: "JAMI:", summa: { formula: `SUM(D2:D${buyurtmalar.length + 1})` } });
jamiQator.font = { bold: true };
ws.getColumn("summa").numFmt = "#,##0"; // raqam formati (ming ajratish)
return (await wb.xlsx.writeBuffer()) as Buffer;
}
// Controller — yuklab berish
@Get("reports/orders.xlsx")
async hisobot(@Res() res: Response) {
const buffer = await this.excelHisobot(await this.ordersService.hammasi());
res.setHeader("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
res.setHeader("Content-Disposition", "attachment; filename=buyurtmalar.xlsx");
res.send(buffer);
}Misol 4 — Excel streaming (katta ma'lumot — 2.7)
// 100000+ qator — streaming writer (xotira to'lmaydi)
@Get("reports/big.xlsx")
async kattaHisobot(@Res() res: Response) {
res.setHeader("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
res.setHeader("Content-Disposition", "attachment; filename=big.xlsx");
const wb = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: res }); // STREAM (2.7)
const ws = wb.addWorksheet("Data");
ws.columns = [{ header: "ID", key: "id" }, { header: "Summa", key: "summa" }];
const dbStream = this.orderRepo.createQueryBuilder("o").stream(); // DB stream (8.3)
dbStream.on("data", (row) => {
ws.addRow({ id: row.o_id, summa: row.o_summa }).commit(); // qator yoz + commit (xotira bo'shaydi)
});
dbStream.on("end", async () => {
await ws.commit();
await wb.commit(); // yakunlash
});
}Misol 5 — CSV eksport stream (2.6, 2.7)
@Get("export/orders.csv")
async csvExport(@Res() res: Response) {
res.setHeader("Content-Type", "text/csv; charset=utf-8");
res.setHeader("Content-Disposition", "attachment; filename=orders.csv");
res.write(""); // UTF-8 BOM (Excel o'zbekni to'g'ri ko'rsatadi — 2.6)
res.write("ID,Mijoz,Summa,Sana\n"); // sarlavha
const stream = this.orderRepo.createQueryBuilder("o").stream();
stream.on("data", (row) => {
const escape = (v: any) => `"${String(v).replace(/"/g, '""')}"`; // escape 2.6-bob
res.write(`${escape(row.o_id)},${escape(row.o_mijoz)},${escape(row.o_summa)},${escape(row.o_sana)}\n`);
});
stream.on("end", () => res.end());
}Misol 6 — CSV import + validatsiya (2.8)
class ImportMahsulotDto {
@IsString() @MinLength(2) nom: string;
@Type(() => Number) @IsNumber() @Min(0) narx: number;
@Type(() => Number) @IsInt() @Min(0) zaxira: number;
}
@Post("products/import")
@UseInterceptors(FileInterceptor("file")) // (8.8)
async import(@UploadedFile(new ParseFilePipe({
validators: [new FileTypeValidator({ fileType: "csv" })], // CSV (8.8)
})) file: Express.Multer.File) {
const records = await this.csvOqi(file.buffer); // parse (2.6)
const natija = { jami: records.length, muvaffaqiyat: 0, xatolar: [] as any[] };
for (const [i, row] of records.entries()) {
const dto = plainToInstance(ImportMahsulotDto, row);
const errors = await validate(dto);
if (errors.length) {
natija.xatolar.push({ // xato hisoboti (qator + sabab)
qator: i + 2, // +2 (sarlavha + 0-indeks)
sabab: errors.map((e) => Object.values(e.constraints || {})).flat(),
});
continue;
}
await this.productsService.yarat(dto);
natija.muvaffaqiyat++;
}
return natija; // { jami: 100, muvaffaqiyat: 95, xatolar: [...] }
}Misol 7 — Excel import (ExcelJS o'qish — 2.8)
async excelOqi(buffer: Buffer): Promise<any[]> {
const wb = new ExcelJS.Workbook();
await wb.xlsx.load(buffer);
const ws = wb.getWorksheet(1);
const rows: any[] = [];
ws.eachRow((row, rowNumber) => {
if (rowNumber === 1) return; // sarlavha o'tkazib yuborish
rows.push({
nom: row.getCell(1).value,
narx: row.getCell(2).value,
zaxira: row.getCell(3).value,
});
});
return rows; // keyin validatsiya (Misol 6 kabi)
}Misol 8 — Fonda hisobot (navbat + bildirishnoma — 2.9)
@Post("reports/request")
@UseGuards(JwtAuthGuard)
async hisobotSora(@Body() dto: ReportRequestDto, @CurrentUser() user) {
const job = await this.reportQueue.add("excel", { filter: dto, userId: user.id });
return { jobId: job.id, status: "tayyorlanmoqda" };
}
@Processor("reports")
export class ReportProcessor extends WorkerHost {
constructor(
private ordersService: OrdersService,
private pdfService: PdfService,
private s3: S3Service,
private notify: NotificationsGateway,
) { super(); }
async process(job: Job) {
const data = await this.ordersService.hisobotData(job.data.filter); // og'ir so'rov
const buffer = await this.excelHisobot(data); // generatsiya (2.5)
const url = await this.s3.yukla(buffer, `report-${job.id}.xlsx`); // S3 (5.11)
await this.notify.foydalanuvchigaYubor(job.data.userId, { // bildirishnoma (8.18)
matn: "Hisobotingiz tayyor", url,
});
}
}Misol 9 — Sertifikat PDF (PDFKit grafika — 2.3)
async sertifikat(ism: string, kurs: string): Promise<Buffer> {
const doc = new PDFDocument({ size: "A4", layout: "landscape", margin: 0 });
const chunks: Buffer[] = [];
doc.on("data", (c) => chunks.push(c));
doc.rect(0, 0, doc.page.width, doc.page.height).fill("#f8f9fa"); // fon
doc.rect(20, 20, doc.page.width - 40, doc.page.height - 40).lineWidth(3).stroke("#4f46e5"); // ramka
doc.fillColor("#4f46e5").fontSize(40).text("SERTIFIKAT", 0, 100, { align: "center" });
doc.fillColor("#333").fontSize(16).text("Ushbu sertifikat", { align: "center" });
doc.fontSize(30).text(ism, { align: "center" });
doc.fontSize(16).text(`"${kurs}" kursini muvaffaqiyatli tamomlagani uchun`, { align: "center" });
doc.end();
return new Promise((resolve) => doc.on("end", () => resolve(Buffer.concat(chunks))));
}Misol 10 — Universal hujjat servis (Strategy — 9.2)
// Format bo'yicha hujjat (Strategy pattern — 9.2)
interface HujjatGenerator {
yarat(data: any[]): Promise<Buffer>;
contentType: string;
}
class PdfGenerator implements HujjatGenerator {
contentType = "application/pdf";
async yarat(data: any[]) { /* PDFKit — 2.3 */ return Buffer.from(""); }
}
class ExcelGenerator implements HujjatGenerator {
contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
async yarat(data: any[]) { /* ExcelJS — 2.5 */ return Buffer.from(""); }
}
class CsvGenerator implements HujjatGenerator {
contentType = "text/csv";
async yarat(data: any[]) { /* CSV — 2.6 */ return Buffer.from(""); }
}
@Injectable()
export class ExportService {
private generators = new Map<string, HujjatGenerator>([
["pdf", new PdfGenerator()], ["xlsx", new ExcelGenerator()], ["csv", new CsvGenerator()],
]);
async export(format: string, data: any[]) {
const gen = this.generators.get(format); // Strategy (9.2)
if (!gen) throw new BadRequestException("Format qo'llab-quvvatlanmaydi");
return { buffer: await gen.yarat(data), contentType: gen.contentType };
}
}
// ?format=pdf|xlsx|csv bir endpoint, ko'p format5. To'g'ri va noto'g'ri holatlar
1) Katta ma'lumot xotirada
100000 qator buffer OOM (2.7)
streaming (qator-qator)2) Puppeteer browser yopilmaydi
har generatsiyada launch, close yo'q (xotira sizishi — 2.4)
browser pool + page.close()3) Import validatsiyasiz
CSV to'g'ridan DB (noto'g'ri ma'lumot — 2.8)
har qator validatsiya + xato hisoboti4) O'zbek harflari PDF'da kvadrat
default shrift (Unicode emas — 2.3)
registerFont (DejaVu/Unicode)5) CSV Excel'da krakozyabra
UTF-8 BOM yo'q (2.6)
res.write("") (BOM)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Out of memory (katta eksport)
Sababi: xotirada to'liq 2.7-bob. Yechimi: streaming.
Xato 2 — Puppeteer sekin/xotira
Sababi: har safar Chrome launch 2.4-bob. Yechimi: browser pool (Misol 2).
Xato 3 — PDF o'zbek harflari buzilgan
Sababi: Unicode shrift yo'q 2.3-bob. Yechimi: registerFont.
Xato 4 — Excel CSV ochishda krakozyabra
Sababi: BOM yo'q 2.6-bob. Yechimi: .
Xato 5 — Import yarmida xato nomutanosib
Sababi: tranzaksiya yo'q 2.8-bob. Yechimi: transaction 8.13-bob yoki qisman rejim + hisobot.
Xato 6 — Og'ir hisobot timeout
Sababi: sinxron generatsiya 2.9-bob. Yechimi: navbat (fonda).
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- Fayl (5.11, 8.8): yuklash/yuklab berish.
- Navbat 8.22-bob: og'ir generatsiya fonda.
- DB stream 8.3-bob: katta ma'lumot.
- DTO 8.5-bob: import validatsiya.
- S3 5.11-bob: hujjat saqlash.
- Bildirishnoma 8.18-bob: "hisobot tayyor".
- Payment 8.19-bob: chek PDF.
- Handlebars 8.10-bob: Puppeteer HTML shablon.
8. Eng yaxshi amaliyotlar (best practices)
- To'g'ri format (PDF/Excel/CSV — maqsadga — 2.1).
- PDF vositasi: PDFKit (grafika/sertifikat), pdfmake (jadval — deklarativ), Puppeteer (HTML/CSS — browser pool, close — 2.2, 2.3b, 2.4).
- O'zbek shrift embed (TTF — DejaVu/Noto/Roboto — 2.3a).
- Handlebars HTML PDF (dizayn ajratilgan — Misol 2a, 8.10).
- StreamableFile qaytar (
@Res()dan afzal — 2.7a). - CSV: fast-csv (stream, auto escape — 2.6a) yoki Papa Parse (sodda — 2.6b).
- Streaming (katta ma'lumot — xotira — 2.7).
- Import validatsiya + xato hisoboti (qator raqami — 2.8).
- Og'ir generatsiya navbat (fonda — 2.9).
- Unicode shrift (o'zbek PDF — 2.3); UTF-8 BOM (CSV Excel — 2.6).
- Excel formula/stil (buxgalter — 2.5).
- CSV escape + injection himoyasi (2.6, 2.10).
- Universal servis (Strategy — format — Misol 10).
- Tranzaksiya (import atomik — 8.13).
9. Amaliy loyiha: "Hujjat va Hisobot Tizimi"
Hujjat generatsiyani amalda mustahkamlash.
Maqsad
To'liq hujjat tizimi: chek PDF, hisobot Excel, eksport CSV (streaming), import (validatsiya), fonda generatsiya.
Talablar (requirements)
- PDF chek: PDFKit + Unicode shrift + stream (Misol 1, 2.3).
- HTML hisob-faktura: Puppeteer + browser pool (Misol 2, 2.4).
- Excel hisobot: stil + formula (Misol 3, 2.5).
- Excel streaming: katta ma'lumot (Misol 4, 2.7).
- CSV eksport: stream + BOM (Misol 5, 2.6).
- CSV import: validatsiya + xato hisoboti (Misol 6, 2.8).
- Excel import: o'qish (Misol 7).
- Fonda hisobot: navbat + bildirishnoma (Misol 8, 2.9).
- Universal servis: Strategy (format — Misol 10).
- Xavfsizlik: CSV injection, shrift, BOM 2.10-bob.
Maslahatlar (hint)
- Streaming katta ma'lumotga (2.7, 1-xato).
- Puppeteer pool (2.4, 2-xato).
- Unicode shrift (2.3, 3-xato).
- BOM (2.6, 4-xato).
- Import validatsiya (2.8, 3-holat).
- Og'ir navbat (2.9, 6-xato).
"Tayyor" mezonlari (acceptance criteria)
- PDF chek (shrift).
- Puppeteer hisob-faktura.
- Excel hisobot (formula).
- Excel streaming.
- CSV eksport (BOM).
- CSV import (validatsiya).
- Excel import.
- Fonda hisobot.
- Universal servis.
- Xavfsizlik.
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda hujjat generatsiyani to'liq o'rgandik:
- Format tanlovi (PDF/Excel/CSV — 2.1); PDF (PDFKit dasturiy — 2.3, Puppeteer HTML — 2.4).
- Excel (ExcelJS — stil, formula — 2.5); CSV (yaratish/parse — 2.6).
- Streaming (katta ma'lumot — xotira — 2.7); import (validatsiya + xato hisoboti — 2.8); fonda (navbat — 2.9).
Keyingi bob — 8.22: Kuchli qidiruv — Elasticsearch, Meilisearch. Hujjatni bildik; endi yana bir real mavzu — kuchli qidiruv (xato-bardosh, tez, ahamiyatli — DB LIKE o'rniga) — ni o'rganamiz. Har e-commerce/kontent platformasida sifatli qidiruv kerak.
Foydalanilgan rasmiy/ishonchli manbalar
- NestJS rasmiy hujjati — Streaming files (
StreamableFile): https://docs.nestjs.com/techniques/streaming-files - PDFKit rasmiy hujjati: https://pdfkit.org/docs/getting_started.html
- pdfmake rasmiy hujjati (declarativ
docDefinition): https://pdfmake.github.io/docs/ - Puppeteer rasmiy hujjati —
page.pdf(): https://pptr.dev/api/puppeteer.page.pdf - ExcelJS rasmiy README (workbook, streaming writer, stil, formula): https://github.com/exceljs/exceljs
- fast-csv rasmiy hujjati (parse va format — stream): https://c2fo.github.io/fast-csv/
- Papa Parse rasmiy hujjati (CSV parse): https://www.papaparse.com/docs
- Node.js rasmiy hujjati — Stream API: https://nodejs.org/api/stream.html
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!