WisarWisar
Dasturlash kitobi/8-QISM — NestJS23 daqiqa

8.18-bob: WebSockets gateway va Task scheduling (cron)

8-QISM — NestJS (chuqur) · 18-mavzu


1. Kirish va motivatsiya

GraphQL'ni 8.17-bob bildik. Endi 8-QISM (NestJS)ning so'nggi ikki amaliy mavzusini — WebSockets (real-time aloqa) va task scheduling (rejalashtirilgan vazifalar — cron) — NestJS'da chuqur ko'ramiz. 5.13'da Socket.io'ni, 5.22'da cron/navbatni Express'da o'rgandik; endi NestJS'da — gateway (@WebSocketGateway) va @nestjs/schedule (@Cron/@Interval/@Timeout) bilan, NestJS arxitekturasida (DI, modul, guard). Bu ikki mavzu — chat/bildirishnoma (WebSocket) va avtomatik davriy ish (cron — tozalash, hisobot, eslatma) — har real ilovada uchraydi.

WebSockets 5.13-bob — server va mijoz orasida doimiy ikki tomonlama aloqa (HTTP'da server faqat so'rovga javob beradi; WebSocket'da server o'zi ham xabar yuboradi — push). Chat, bildirishnoma, jonli yangilanish (narx, o'yin, kuzatuv) uchun. NestJS'da gateway — controller'ning WebSocket versiyasi (@WebSocketGateway, @SubscribeMessage). Task scheduling 5.22-bob — kodni belgilangan vaqtda avtomatik ishga tushirish: har kecha hisobot, har soat tozalash, eslatma yuborish.

Bu bob: WebSocket nima, gateway, @SubscribeMessage, xonalar (rooms), broadcast, gateway lifecycle, guard; va task scheduling — @Cron (cron ifoda), @Interval, @Timeout, dinamik vazifa, va production best practice (idempotent, distributed lock). Bu bob 5.13, 5.22 ni NestJS'ga ko'taradi, va 8-QISM (NestJS)ni yakunlaydi. Real-time va avtomatlashtirish — zamonaviy ilova xususiyatlari.

O'xshatish: WebSocket — telefon qo'ng'irog'i (HTTP — xat almashish: har safar yangi xat yuborasiz-kutasiz; WebSocket — ochiq liniya: ikkalangiz istalgan payt gapirasiz — 5.13). Task scheduling (cron) — budilnik va taymer: "har kuni 09:00 da hisobot tayyorla", "har 10 daqiqada keshni tozala", "1 soatdan keyin eslatma yubor" — siz uxlaganingda ham ilova o'z ishini avtomatik bajaradi (qo'lda emas). Ikkalasi — ilovani "jonli" va "aqlli" qiladi.

Nega muhim?

  • Real-time — chat, bildirishnoma, jonli yangilanish (WebSocket — 5.13).
  • Avtomatlashtirish — davriy ish (hisobot, tozalash — cron — 5.22).
  • NestJS uslub — gateway (DI, guard), @Cron (dekorator).
  • 8-QISM yakuni — to'liq backend stack (NestJS).

2. Nazariya — chuqur tushuntirish

2.1. WebSocket asoslari (5.13 takrori)

text
  HTTP 5.5-bob:  mijoz so'raydi  server javob (bir tomonlama, har safar yangi)
  WebSocket:   doimiy ulanish  ikki tomonlama (server PUSH qila oladi — 5.13)

  Qachon WebSocket:
   Chat, bildirishnoma (server  mijoz xabar)
   Jonli yangilanish (narx, sport, kuzatuv)
   Hamkorlik (birga tahrirlash)
   Oddiy CRUD (REST yetadi — 5.7)

  NestJS: Socket.io (default — reconnect, room, fallback) yoki ws

WebSocket 5.13-bob — doimiy, ikki tomonlama aloqa (server push qila oladi — HTTP'dan farq). Chat, bildirishnoma, jonli yangilanish uchun. NestJS — Socket.io (default — qayta ulanish, xona, fallback — 5.13: 2.2) yoki ws (yengil). Oddiy CRUD'ga REST 5.7-bob. Bu — 5.13 asosi (NestJS'da gateway).

2.2. Gateway (@WebSocketGateway)

ts
import { WebSocketGateway, WebSocketServer, SubscribeMessage, MessageBody } from "@nestjs/websockets";
import { Server } from "socket.io";

@WebSocketGateway({ cors: { origin: "*" } })         // WebSocket "controller"
export class ChatGateway {
  @WebSocketServer() server: Server;                 // Socket.io server (broadcast uchun)

  @SubscribeMessage("xabar")                         // hodisa tinglovchi (route kabi)
  xabarKeldi(@MessageBody() data: string) {
    this.server.emit("xabar", data);                 // hammaga yuborish (broadcast — 5.13)
  }
}

Gateway — WebSocket'ning "controller"i (@WebSocketGateway). @WebSocketServer() — Socket.io server (broadcast — 2.5). @SubscribeMessage("hodisa") — hodisa tinglovchi (controller route kabi — 8.1). @MessageBody() — ma'lumot. DI bilan service 8.2-bob. Bu — 5.13 Socket.io'ning NestJS dekorator versiyasi.

Gateway ham provider — u @Modulening providers ro'yxatiga qo'shilishi shart (controller emas, provider — chunki DI orqali boshqa service'ga inject bo'la oladi — Misol 3'da OrdersService gateway'ni chaqiradi). Aks holda gateway umuman ishga tushmaydi (xato 3'ning WebSocket varianti).

ts
@Module({
  providers: [ChatGateway, ChatService],   // gateway — provider (controller EMAS)
})
export class ChatModule {}

2.2b. @WebSocketGateway parametrlari (port, namespace, cors)

ts
// 1) Standart — HTTP server bilan bir portda (Nest app porti — eng keng tarqalgan)
@WebSocketGateway({ cors: { origin: "*" } })

// 2) Alohida port — birinchi argument port raqami
@WebSocketGateway(3001, { transports: ["websocket"] })   // 3001-portda mustaqil

// 3) namespace — bir server ichida mantiqiy bo'linma (Socket.io namespace — 5.13)
@WebSocketGateway({ namespace: "chat", cors: { origin: "*" } })
// mijoz: io("http://localhost:3000/chat")    namespace'ga ulanadi

// 4) cors — production'da aniq domenlar (origin: "*" — faqat dev!)
@WebSocketGateway({
  cors: { origin: ["https://app.example.uz"], credentials: true },
})

// 5) path — WebSocket endpoint yo'li (default: /socket.io)
@WebSocketGateway({ path: "/ws" })

Parametrlar: birinchi (ixtiyoriy) argument — port (masalan 3001 — HTTP server'dan alohida; berilmasa Nest HTTP porti bilan birga); ikkinchi — options obyekti. namespace — bir WebSocket server ichida mantiqiy kanal (/chat, /notifications — har biri o'z gateway'i — Misol 1 va 3; 5.13 namespace tushunchasi). cors — brauzerdan ulanishga ruxsat (origin — dev'da "*", production'da aniq domenlar, credentials: true — cookie bilan). transports["websocket"] (polling'siz), path — endpoint yo'li. Bu parametrlar — 5.13'dagi io(server, options) sozlamalarining dekorator ko'rinishi.

2.2c. socket.io vs ws — adapter tanlash (IoAdapter / WsAdapter)

ts
// NestJS gateway ostidagi kutubxonani ADAPTER belgilaydi (default — Socket.io)

// Variant A: Socket.io (default) — hech narsa qilmasangiz shu
//   npm i @nestjs/platform-socket.io socket.io
//   Afzalligi: qayta ulanish, xona, fallback (polling), acknowledgement (5.13)

// Variant B: sof ws (yengil, standart WebSocket protokoli)
//   npm i @nestjs/platform-ws ws
import { WsAdapter } from "@nestjs/platform-ws";
// main.ts:
app.useWebSocketAdapter(new WsAdapter(app));   // Socket.io O'RNIGA sof ws

Adapter — gateway ostida qaysi WebSocket kutubxonasi ishlashini belgilaydi. IoAdapter (default — @nestjs/platform-socket.io) Socket.io: qayta ulanish, xona (room), transport fallback (polling), acknowledgement — real-time ilova uchun eng qulay (5.13: 2.2). WsAdapter (@nestjs/platform-ws) sof ws: yengil, standart WebSocket protokoli, lekin room/reconnect o'zingiz yozasiz. wsda xabar format farqi: { event, data } JSON kutiladi. Ko'pincha Socket.io tanlanadi (imkoniyat boy); brauzersiz IoT/yengil holatda ws. RedisIoAdapter 2.9-bobIoAdapterni kengaytiradi (masshtab uchun).

2.3. Client va Socket (ulanish)

ts
import { ConnectedSocket } from "@nestjs/websockets";
import { Socket } from "socket.io";

@SubscribeMessage("xabar")
xabar(@MessageBody() data: any, @ConnectedSocket() client: Socket) {
  console.log("Kimdan:", client.id);                 // ulanish ID
  client.emit("javob", "qabul qilindi");             // FAQAT shu mijozga
  client.broadcast.emit("yangi", data);              // o'zidan boshqa hammaga
}

Socket (@ConnectedSocket()) — bitta ulanish (mijoz). client.id (noyob), client.emit (faqat shu mijozga), client.broadcast.emit (o'zidan boshqa hammaga), this.server.emit (hammaga — 2.2). Yuborish maqsadiga qarab tanlanadi (5.13: 2.7). Bu — kim kimga xabar olishini boshqaradi.

2.4. Gateway lifecycle (ulanish hodisalari)

ts
import {
  OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit,
} from "@nestjs/websockets";
import { Server, Socket } from "socket.io";

@WebSocketGateway()
export class ChatGateway
  implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {

  afterInit(server: Server) {                        // server TAYYOR bo'lganda (bir marta)
    console.log("WebSocket server ishga tushdi");
    // global middleware, adapter sozlash mumkin (2.9)
    // server.use((socket, next) => { ... });
  }

  handleConnection(client: Socket, ...args: any[]) { // har mijoz ulanganda
    console.log(`Ulandi: ${client.id}`);
    // auth tekshirish, online belgilash 2.8-bob;  tekshiruv o'tmasa: client.disconnect()
  }
  handleDisconnect(client: Socket) {                 // har mijoz uzilganda
    console.log(`Uzildi: ${client.id}`);
    // offline belgilash, xonadan chiqarish, userSockets.delete (Misol 3)
  }
}

Gateway lifecycle — uch interfeys: OnGatewayInit afterInit(server) (WebSocket server tayyor bo'lganda bir marta — global middleware/adapter sozlash); OnGatewayConnection handleConnection(client) (har mijoz ulanganda — auth, online belgilash — auth o'tmasa client.disconnect()); OnGatewayDisconnect handleDisconnect(client) (har mijoz uzilganda — offline, Mapdan o'chirish, xonadan chiqarish). Online/offline holatni boshqarish (5.13: 2.12), ulanishda auth 2.8-bob. Bu — ulanish hayot siklini boshqarish (chat'da kim online).

2.5. Xonalar (rooms) va broadcast (5.13)

ts
@SubscribeMessage("xonaga_qoshil")
xonagaQoshil(@MessageBody() xonaId: string, @ConnectedSocket() client: Socket) {
  client.join(xonaId);                               // xonaga kirish (5.13: 2.9)
  client.to(xonaId).emit("foydalanuvchi_qoshildi", client.id);
}

@SubscribeMessage("xona_xabar")
xonaXabar(@MessageBody() data: { xonaId: string; matn: string }) {
  this.server.to(data.xonaId).emit("xabar", data.matn);   // FAQAT shu xonaga (5.13)
}

Xonalar (rooms — 5.13: 2.9): client.join(xona) (kirish), client.leave(xona) (chiqish), server.to(xona).emit (faqat shu xonaga). Guruh chati, o'yin xonasi, mavzuga obuna uchun. Broadcast turlari (5.13: 2.7): hammaga, xonaga, bittaga. Bu — maqsadli yuborish (samarali — kerakli odamlarga).

2.6. Javob qaytarish va validatsiya

ts
import { WsResponse } from "@nestjs/websockets";
import { UsePipes, ValidationPipe } from "@nestjs/common";

@SubscribeMessage("ping")
ping(): WsResponse<string> {
  return { event: "pong", data: "javob" };           // javob (acknowledgement)
}

@SubscribeMessage("xabar")
@UsePipes(new ValidationPipe())                      // validatsiya (8.5)
xabar(@MessageBody() dto: ChatMessageDto) {          // DTO (8.5)
  // dto validatsiyalangan
}

Javob/validatsiya: WsResponse ({ event, data }) — mijozga javob (acknowledgement). ValidationPipe 8.5-bob WebSocket'da ham (DTO validatsiya — message payload). Guard/interceptor/filter ham (8.5, 8.6) — WsException (xato). NestJS imkoniyatlari gateway'da (moslashtirilgan — 2.8).

2.7. WsException va xato (8.6)

ts
import { WsException } from "@nestjs/websockets";

@SubscribeMessage("xabar")
xabar(@MessageBody() data: any) {
  if (!data.matn) throw new WsException("Matn bo'sh");   // WS xato (HTTP emas)
}

// WS exception filter (8.6)
@Catch(WsException)
export class WsExceptionFilter implements ExceptionFilter {
  catch(exception: WsException, host: ArgumentsHost) {
    const client = host.switchToWs().getClient();
    client.emit("error", { message: exception.message });
  }
}

WsException — WebSocket xato (HTTP exception emas — 8.1). Mijozga error hodisasi sifatida yuboriladi. WS exception filter (@Catch(WsException) — 8.6) — markaziy WS xato. Validatsiya xatosi ham WS'ga moslanadi. Bu — gateway'da xato boshqaruv (5.10 WS versiyasi).

2.8. Gateway auth (guard — 8.9)

ts
// WS guard — token tekshirish (ulanish/xabar)
@Injectable()
export class WsJwtGuard implements CanActivate {
  constructor(private jwtService: JwtService) {}
  canActivate(context: ExecutionContext): boolean {
    const client = context.switchToWs().getClient<Socket>();
    const token = client.handshake.auth?.token;       // ulanishda token (5.13: 2.13)
    try {
      const payload = this.jwtService.verify(token);
      client.data.user = payload;                     // socket'ga user
      return true;
    } catch { throw new WsException("Avtorizatsiya yo'q"); }
  }
}

@UseGuards(WsJwtGuard)
@SubscribeMessage("xabar")
xabar(@MessageBody() data: any, @ConnectedSocket() client: Socket) {
  const user = client.data.user;                      // autentifikatsiyalangan
}

Gateway auth (5.13: 2.13, 8.9) — WebSocket'da ham auth kerak (kim ulanyapti). Token handshake.auth (ulanishda) yoki har xabarda. WS guard (canActivate — 8.6) token tekshiradi client.data.user. WebSocket — HTTP cookie/header'dan farq (handshake). Auth — chat/real-time xavfsizligi (14).

2.9. Redis adapter (masshtab — ko'p instance)

ts
// Ko'p instance'da WebSocket (Redis adapter — 5.13: 2.15)
import { IoAdapter } from "@nestjs/platform-socket.io";
import { createAdapter } from "@socket.io/redis-adapter";

export class RedisIoAdapter extends IoAdapter {
  createIOServer(port: number, options?: any) {
    const server = super.createIOServer(port, options);
    const pubClient = createClient({ url: "redis://localhost:6379" });
    const subClient = pubClient.duplicate();
    server.adapter(createAdapter(pubClient, subClient));   // Redis adapter
    return server;
  }
}
// main.ts: app.useWebSocketAdapter(new RedisIoAdapter(app));

Redis adapter (5.13: 2.15) — ko'p server instance'da (10 — masshtab) WebSocket sinxronizatsiyasi. Bir instance'dagi broadcast boshqa instance'dagi mijozlarga ham (Redis pub/sub orqali). Production'da bir nechta instance bo'lsa majburiy (aks holda mijozlar turli instance'da — xabar yetmaydi). Gorizontal masshtab (10).

2.10. Task scheduling — @nestjs/schedule (5.22)

ts
// app.module.ts
import { ScheduleModule } from "@nestjs/schedule";

@Module({
  imports: [ScheduleModule.forRoot()],               // scheduler yoqish
})
export class AppModule {}

Task scheduling (@nestjs/schedule — 5.22): ScheduleModule.forRoot() — rejalashtirishni yoqadi. Uch tur: @Cron (cron ifoda — vaqt — 2.11), @Interval (har N ms — 2.12), @Timeout (bir marta N ms keyin). Avtomatik davriy ish (5.22 — node-cron'ning NestJS versiyasi). Tozalash, hisobot, eslatma.

2.11. @Cron (cron ifoda — vaqt bo'yicha)

ts
import { Cron, CronExpression } from "@nestjs/schedule";

@Injectable()
export class TasksService {
  private logger = new Logger(TasksService.name);

  @Cron("0 9 * * *")                                 // har kuni 09:00 (cron ifoda — 5.22)
  ertalabkiHisobot() {
    this.logger.log("Ertalabki hisobot tayyorlanmoqda");
  }

  @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)        // enum (o'qiladi — 5.22)
  yarimTunTozalash() {
    this.logger.log("Eski ma'lumotlar tozalanmoqda");
  }

  @Cron("0 */6 * * *", { timeZone: "Asia/Tashkent" })   // har 6 soat, vaqt zonasi
  davriy() {}
}

@Cron 5.22-bob — cron ifoda bilan vaqt ("0 9 * * *" — har kuni 09:00). CronExpression enum (o'qiladi — EVERY_DAY_AT_MIDNIGHT). timeZone (Asia/Tashkent — server vaqti har xil bo'lishi mumkin — best practice). Cron ifoda: daqiqa soat kun oy hafta-kuni (5.22: 2.5). Davriy hisobot, tozalash, eslatma.

Cron ifoda tuzilishi va namunalar (5 maydon; @nestjs/schedule cron kutubxonasidan — 6 maydon bilan soniya ham qo'shsa bo'ladi):

text
   ┌──────────── soniya (0-59)    ixtiyoriy (6-maydonli variant)
   │ ┌────────── daqiqa (0-59)
   │ │ ┌──────── soat   (0-23)
   │ │ │ ┌────── oy kuni (1-31)
   │ │ │ │ ┌──── oy     (1-12 yoki JAN-DEC)
   │ │ │ │ │ ┌── hafta kuni (0-7; 0 va 7 = yakshanba yoki SUN-SAT)
   │ │ │ │ │ │
   * * * * * *
   Belgilar: *  = har  |  */5 = har 5  |  1,15 = 1 va 15  |  1-5 = 1 dan 5 gacha

   Namunalar:
   "* * * * *"        — har daqiqa
   "0 * * * *"        — har soat boshida (xx:00)
   "*/15 * * * *"     — har 15 daqiqada
   "0 9 * * *"        — har kuni 09:00
   "0 0 * * 0"        — har yakshanba yarim tunda
   "0 8 * * 1-5"      — dushanba–juma, 08:00 (ish kunlari)
   "0 0 1 * *"        — har oyning 1-kuni yarim tunda
   "30 2 * * 1"       — har dushanba 02:30

Ko'p ishlatiladigan CronExpression enum qiymatlari (raqam yodlamaslik uchun — o'qiladigan nom):

text
   EVERY_SECOND                     "* * * * * *"
   EVERY_10_SECONDS                 "*/10 * * * * *"
   EVERY_MINUTE                     "* * * * *"
   EVERY_5_MINUTES                  "*/5 * * * *"
   EVERY_30_MINUTES                 "0,30 * * * *"
   EVERY_HOUR                       "0 * * * *"
   EVERY_DAY_AT_MIDNIGHT            "0 0 * * *"
   EVERY_DAY_AT_NOON                "0 12 * * *"
   EVERY_DAY_AT_9AM                 "0 9 * * *"
   EVERY_WEEK                       "0 0 * * 0"
   EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT   "0 0 1 * *"

Murakkab ifodani qo'lda yozmang — CronExpressionda mos qiymat bo'lsa, o'shani ishlating (o'qiladi, xato kamayadi). Ifodani tekshirish uchun crontab.guru 5.22-bob qulay.

2.12. @Interval va @Timeout

ts
@Interval(10000)                                     // har 10 soniya (ms)
har10Soniya() {
  this.logger.log("Har 10 soniyada");                // monitoring, health
}

@Timeout(5000)                                       // ishga tushgandan 5 soniya keyin (bir marta)
boshlanishda() {
  this.logger.log("Ilova boshlanganda bir marta");   // warm-up, dastlabki yuklash
}

@Interval (har N ms — 2.12), @Timeout (N ms keyin bir marta). Interval — muntazam (monitoring, health — 8.15); Timeout — kechiktirilgan boshlanish (warm-up, kesh to'ldirish). @Cron'dan farq: ms asosida (vaqt emas). Oddiy davriy/kechiktirilgan ish uchun.

2.13. Dinamik vazifa (runtime — SchedulerRegistry)

ts
import { SchedulerRegistry } from "@nestjs/schedule";
import { CronJob } from "cron";

@Injectable()
export class DynamicTasksService {
  constructor(private scheduler: SchedulerRegistry) {}

  // Runtime'da cron qo'shish (foydalanuvchi belgilagan eslatma)
  eslatmaQosh(nom: string, vaqt: string, ish: () => void) {
    const job = new CronJob(vaqt, ish);
    this.scheduler.addCronJob(nom, job);              // ro'yxatga
    job.start();
  }
  eslatmaOchir(nom: string) {
    this.scheduler.deleteCronJob(nom);                // o'chirish
  }
}

Dinamik vazifa (SchedulerRegistry) — runtime'da cron qo'shish/o'chirish (kodda emas — foydalanuvchi belgilagan eslatma, sozlamadan jadval). addCronJob/deleteCronJob/getCronJob. Foydalanuvchi-boshqaradigan rejalar (eslatma ilovasi) uchun. Statik (@Cron) yetmaganda.

SchedulerRegistry — to'liq API (cron, interval, timeout — hammasini runtime'da boshqarish):

ts
constructor(private scheduler: SchedulerRegistry) {}

// --- CRON (vaqt bo'yicha) ---
this.scheduler.addCronJob("nom", new CronJob("0 9 * * *", () => {}));
this.scheduler.getCronJob("nom").stop();            // vaqtincha to'xtatish
this.scheduler.getCronJob("nom").start();           // qayta yoqish
const jobs = this.scheduler.getCronJobs();          // Map<string, CronJob>
this.scheduler.deleteCronJob("nom");

// keyingi ishga tushish vaqti (foydalanuvchiga ko'rsatish uchun)
const next = this.scheduler.getCronJob("nom").nextDate();  // DateTime

// --- INTERVAL (har N ms — runtime) ---
const id = setInterval(() => this.tekshir(), 5000);
this.scheduler.addInterval("tekshiruv", id);
this.scheduler.deleteInterval("tekshiruv");         // clearInterval ham chaqiradi

// --- TIMEOUT (N ms keyin bir marta — runtime) ---
const t = setTimeout(() => this.eslat(), 10000);
this.scheduler.addTimeout("eslatma", t);
this.scheduler.deleteTimeout("eslatma");

To'liq registry API: cron uchun addCronJob/getCronJob/getCronJobs/deleteCronJob (job'ni .stop()/.start()/.nextDate() bilan boshqarish); interval uchun addInterval/deleteInterval; timeout uchun addTimeout/deleteTimeout (o'chirishda ular clearInterval/clearTimeoutni ham chaqiradi). nextDate() — keyingi ishga tushish vaqti (foydalanuvchiga "keyingi eslatma: ..." deb ko'rsatish uchun qulay). Bu — barcha rejalarni bir joydan (registry) markazlashgan boshqarish.

2.14. Cron production best practice (muhim)

text
   Vazifa YENGIL (orkestratsiya — og'ir ish service/navbatga — 5.22, 8.22)
   timeZone aniq (Asia/Tashkent — 2.11)
   IDEMPOTENT (ikki marta ishlasa — buzilmasin — 5.22)
   DISTRIBUTED LOCK (ko'p instance — bir vazifa bir marta! — eng muhim)
   Og'ir/qayta urinish  navbat (BullMQ — 8.22)
   Cron ifoda config'dan (muhitga bog'liq — 8.14)
   Xato boshqaruv (cron xatosi log — 8.15)

Cron best practice 5.22-bob: vazifa yengil (og'ir ish navbatga — 8.22); timeZone aniq; idempotent (ikki marta = bir marta); distributed lock (ko'p instance'da — bir vazifa faqat bir marta ishlashi kerak — aks holda hisobot 3 marta yuboriladi! Redis lock — 8.15). Og'ir/retry BullMQ 8.22-bob. Bu — production cron'ning muhim qoidalari.

2.15. WebSocket vs alternativlar (tanlov)

text
  Real-time variantlar 5.13-bob:
  WebSocket    — to'liq ikki tomonlama (chat, o'yin) — eng kuchli
  SSE          — servermijoz bir tomonlama (bildirishnoma — yengilroq)
  Polling      — mijoz so'rab turadi (oddiy, lekin samarasiz)
  GraphQL Sub  — GraphQL real-time 8.17-bob

   Ikki tomonlama kerak  WebSocket; faqat server push  SSE

Real-time tanlov 5.13-bob: WebSocket (to'liq ikki tomonlama — chat/o'yin), SSE (servermijoz — bildirishnoma — yengilroq), polling (oddiy — samarasiz), GraphQL subscription 8.17-bob. Ikki tomonlama interaktivlik WebSocket; faqat server xabari SSE yetadi. To'g'ri vosita (eng murakkabini doim tanlamaslik).

2.16. Best practices (WebSocket + cron)

text
  WEBSOCKET:
   Socket.io (reconnect/room — 2.1); gateway yupqa  service 8.1-bob
   Xonalar (maqsadli broadcast — 2.5); auth guard 2.8-bob
   Redis adapter (ko'p instance — 2.9); validatsiya (DTO — 2.6)
   Lifecycle (online/offline — 2.4); WsException filter 2.7-bob

  CRON:
   Yengil vazifa (og'ir  navbat — 2.14, 8.22)
   timeZone; idempotent; distributed lock (ko'p instance — 2.14)
   CronExpression enum (o'qiladi — 2.11); config'dan jadval 8.14-bob
   To'g'ri tur (@Cron/@Interval/@Timeout — 2.11, 2.12)

3. Sintaksis — tez ma'lumotnoma

ts
// WebSocket (2.2, 2.5)
@WebSocketGateway({ cors: { origin: "*" } })
@WebSocketServer() server: Server;
@SubscribeMessage("hodisa")  handle(@MessageBody() data, @ConnectedSocket() client) {}
client.join(xona);  server.to(xona).emit("event", data);  // xona broadcast

// Cron (2.10, 2.11, 2.12)
ScheduleModule.forRoot()
@Cron("0 9 * * *")  @Cron(CronExpression.EVERY_HOUR)
@Interval(10000)  @Timeout(5000)
// Dinamik: schedulerRegistry.addCronJob(nom, new CronJob(...))

4. Batafsil kod namunalari

Misol 1 — Chat gateway (to'liq — 2.2, 2.4, 2.5)

ts
@WebSocketGateway({ cors: { origin: "*" }, namespace: "chat" })
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer() server: Server;
  private logger = new Logger("ChatGateway");

  constructor(private chatService: ChatService) {}   // DI (8.2)

  handleConnection(client: Socket) {
    this.logger.log(`Ulandi: ${client.id}`);
  }
  handleDisconnect(client: Socket) {
    this.logger.log(`Uzildi: ${client.id}`);
  }

  @SubscribeMessage("xonaga_qoshil")
  async xonagaQoshil(@MessageBody() xonaId: string, @ConnectedSocket() client: Socket) {
    client.join(xonaId);                             // xona (2.5)
    const tarix = await this.chatService.xonaTarixi(xonaId);
    client.emit("tarix", tarix);                     // faqat shu mijozga
    client.to(xonaId).emit("foydalanuvchi_qoshildi", client.id);
  }

  @SubscribeMessage("xabar")
  async xabar(
    @MessageBody() dto: SendMessageDto,             // DTO (8.5)
    @ConnectedSocket() client: Socket,
  ) {
    const xabar = await this.chatService.saqla(dto);   // service (8.1)
    this.server.to(dto.xonaId).emit("xabar", xabar);   // xonaga broadcast
    return { event: "yuborildi", data: xabar.id };     // ack
  }

  @SubscribeMessage("yozmoqda")
  yozmoqda(@MessageBody() data: any, @ConnectedSocket() client: Socket) {
    client.to(data.xonaId).emit("yozmoqda", data.userId);   // "yozmoqda..." (5.13)
  }
}

Misol 2 — Gateway auth (WS guard — 2.8)

ts
@Injectable()
export class WsJwtGuard implements CanActivate {
  constructor(private jwtService: JwtService, private config: ConfigService) {}

  canActivate(context: ExecutionContext): boolean {
    const client = context.switchToWs().getClient<Socket>();
    const token = client.handshake.auth?.token || client.handshake.headers?.authorization?.split(" ")[1];
    if (!token) throw new WsException("Token yo'q");
    try {
      const payload = this.jwtService.verify(token, { secret: this.config.get("jwt.accessSecret") });
      client.data.user = payload;                     // socket'ga user (8.9)
      return true;
    } catch {
      throw new WsException("Token yaroqsiz");
    }
  }
}

// Gateway'da
@UseGuards(WsJwtGuard)
@SubscribeMessage("xabar")
xabar(@MessageBody() dto: SendMessageDto, @ConnectedSocket() client: Socket) {
  const user = client.data.user;                      // autentifikatsiyalangan
  return this.chatService.saqla({ ...dto, userId: user.sub });
}

Misol 3 — Bildirishnoma gateway (server push — 2.3)

ts
// Boshqa service'dan foydalanuvchiga real-time bildirishnoma
@WebSocketGateway({ namespace: "notifications" })
export class NotificationsGateway implements OnGatewayConnection {
  @WebSocketServer() server: Server;
  private userSockets = new Map<number, string>();   // userId  socketId

  handleConnection(client: Socket) {
    const userId = client.data.user?.id;             // (auth — 2.8)
    if (userId) this.userSockets.set(userId, client.id);
  }

  // Service'lar chaqiradi (buyurtma holati o'zgarganda)
  foydalanuvchigaYubor(userId: number, bildirishnoma: any) {
    const socketId = this.userSockets.get(userId);
    if (socketId) {
      this.server.to(socketId).emit("bildirishnoma", bildirishnoma);   // shu user'ga
    }
  }
}

// OrdersService — bildirishnoma yuborish
@Injectable()
export class OrdersService {
  constructor(private notificationsGateway: NotificationsGateway) {}
  async holatYangila(orderId: number, holat: string) {
    const order = await this.repo.update(orderId, { holat });
    this.notificationsGateway.foydalanuvchigaYubor(order.userId, {
      matn: `Buyurtmangiz holati: ${holat}`,
    });
  }
}

Misol 4 — Cron vazifalar (2.11, 2.12)

ts
@Injectable()
export class TasksService {
  private logger = new Logger(TasksService.name);
  constructor(
    private reportsService: ReportsService,
    private usersService: UsersService,
  ) {}

  // Har kuni 09:00 — kunlik hisobot (2.11)
  @Cron("0 9 * * *", { timeZone: "Asia/Tashkent" })
  async kunlikHisobot() {
    this.logger.log("Kunlik hisobot boshlandi");
    await this.reportsService.kunlikHisobotYubor();   // yengil orkestratsiya (2.14)
  }

  // Har yarim tunda — eski sessiyalarni tozalash
  @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
  async tozalash() {
    const ochirilgan = await this.usersService.eskiSessiyalarOchir();
    this.logger.log(`${ochirilgan} ta eski sessiya o'chirildi`);
  }

  // Har 30 daqiqada — tasdiqlanmagan buyurtmalarni eslatish
  @Cron(CronExpression.EVERY_30_MINUTES)
  async eslatma() {
    await this.reportsService.tasdiqlanmaganEslatma();
  }

  // Har soat — health metrika (2.12)
  @Interval(3600000)
  metrika() {
    this.logger.log(`Xotira: ${process.memoryUsage().heapUsed / 1024 / 1024} MB`);
  }
}

Misol 5 — Dinamik eslatma (runtime cron — 2.13)

ts
@Injectable()
export class RemindersService {
  constructor(
    private scheduler: SchedulerRegistry,
    private notificationsGateway: NotificationsGateway,
  ) {}

  // Foydalanuvchi eslatma o'rnatadi (runtime)
  eslatmaYarat(userId: number, vaqt: string, matn: string) {
    const nom = `eslatma_${userId}_${Date.now()}`;
    const job = new CronJob(vaqt, () => {
      this.notificationsGateway.foydalanuvchigaYubor(userId, { matn });   // (Misol 3)
      this.scheduler.deleteCronJob(nom);             // bir martalik  o'chirish
    });
    this.scheduler.addCronJob(nom, job);
    job.start();
    return nom;
  }

  eslatmaBekor(nom: string) {
    this.scheduler.deleteCronJob(nom);
  }

  faolEslatmalar() {
    return [...this.scheduler.getCronJobs().keys()];
  }
}

Misol 6 — Distributed lock (ko'p instance — 2.14)

ts
// Ko'p instance'da cron — faqat bir marta (Redis lock — eng muhim)
@Injectable()
export class TasksService {
  constructor(@InjectRedis() private redis: Redis) {}

  @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
  async hisobot() {
    // Distributed lock — faqat bitta instance bajaradi (2.14)
    const lock = await this.redis.set("lock:hisobot", "1", "EX", 300, "NX");
    if (!lock) {
      return;                                         // boshqa instance band — chiq
    }
    try {
      await this.reportsService.hisobotYubor();       // faqat bir marta!
    } finally {
      await this.redis.del("lock:hisobot");
    }
  }
}
//  Locksiz: 3 instance  hisobot 3 marta yuboriladi (xato!)

Misol 7 — Cron + navbat (og'ir ish — 2.14, 8.22)

ts
// Cron yengil (faqat navbatga qo'yadi — og'ir ish worker'da — 8.22)
@Injectable()
export class TasksService {
  constructor(@InjectQueue("reports") private queue: Queue) {}

  @Cron("0 8 * * 1")                                 // har dushanba 08:00
  async haftalikHisobot() {
    const userlar = await this.usersService.adminlar();
    for (const user of userlar) {
      await this.queue.add("haftalik", { userId: user.id });   // navbatga (yengil — 2.14)
    }
    // Cron tez tugaydi; og'ir hisobot worker'da (8.22)
  }
}

Misol 8 — Gateway DTO validatsiya (2.6)

ts
export class SendMessageDto {
  @IsString() @IsNotEmpty()
  xonaId: string;

  @IsString() @MinLength(1) @MaxLength(1000)
  matn: string;
}

// main.ts — global ValidationPipe (WebSocket'da ham — 8.5)
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));

// Gateway'da
@SubscribeMessage("xabar")
xabar(@MessageBody() dto: SendMessageDto) {           // validatsiyalangan
  return this.chatService.saqla(dto);
}

Misol 9 — Redis adapter setup (masshtab — 2.9)

ts
// redis-io.adapter.ts
import { IoAdapter } from "@nestjs/platform-socket.io";
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";

export class RedisIoAdapter extends IoAdapter {
  private adapterConstructor: ReturnType<typeof createAdapter>;

  async connectToRedis(url: string): Promise<void> {
    const pubClient = createClient({ url });
    const subClient = pubClient.duplicate();
    await Promise.all([pubClient.connect(), subClient.connect()]);
    this.adapterConstructor = createAdapter(pubClient, subClient);
  }

  createIOServer(port: number, options?: any) {
    const server = super.createIOServer(port, options);
    server.adapter(this.adapterConstructor);
    return server;
  }
}

// main.ts
const redisAdapter = new RedisIoAdapter(app);
await redisAdapter.connectToRedis("redis://localhost:6379");
app.useWebSocketAdapter(redisAdapter);
//  ko'p instance'da WebSocket sinxron (2.9)

Misol 10 — To'liq real-time + scheduled tizim

text
  Modul tuzilishi (8-QISM yakuni):
  ├── chat/
  │   ├── chat.gateway.ts        (WebSocket — Misol 1, 2)
  │   └── chat.service.ts
  ├── notifications/
  │   └── notifications.gateway.ts   (server push — Misol 3)
  ├── tasks/
  │   ├── tasks.service.ts       (@Cron — Misol 4, 6, 7)
  │   └── reminders.service.ts   (dinamik — Misol 5)
  └── main.ts                    (Redis adapter — Misol 9)

  Oqim:
  - Chat: WebSocket (xona, auth, real-time)
  - Bildirishnoma: service  gateway  mijoz (push)
  - Cron: hisobot/tozalash (lock, navbat)
  - Eslatma: dinamik cron  bildirishnoma

5. To'g'ri va noto'g'ri holatlar

1) Ko'p instance cron locksiz

text
 3 instance  vazifa 3 marta (2.14)
 distributed lock (Redis — bir marta)

2) Og'ir ish cron ichida

text
 cron'da og'ir hisobot (bloklaydi — 2.14)
 navbatga (BullMQ — worker'da — 8.22)

3) WebSocket auth'siz

text
 kim ulansa, xabar (14)
 WS guard (token — 2.8)

4) Ko'p instance WebSocket Redis adapter'siz

text
 mijozlar turli instance'da  xabar yetmaydi (2.9)
 Redis adapter

5) Cron timeZone'siz

text
 server vaqti (noto'g'ri vaqtda — 2.11)
 timeZone: "Asia/Tashkent"

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — WebSocket ulanmaydi (CORS)

Sababi: cors sozlanmagan 2.2-bob. Yechimi: @WebSocketGateway({ cors: {...} }).

Xato 2 — Broadcast yetmaydi (ko'p instance)

Sababi: Redis adapter yo'q 2.9-bob. Yechimi: Redis adapter (Misol 9).

Xato 3 — Cron ishlamaydi

Sababi: ScheduleModule.forRoot yo'q, yoki provider emas 2.10-bob. Yechimi: forRoot; service provider.

Xato 4 — Cron ikki marta (ko'p instance)

Sababi: lock yo'q 2.14-bob. Yechimi: distributed lock (Misol 6).

Xato 5 — Cron noto'g'ri vaqtda

Sababi: timeZone yo'q 2.11-bob. Yechimi: timeZone aniq.

Xato 6 — WS guard ishlamaydi

Sababi: context HTTP (WS boshqacha — 2.8). Yechimi: switchToWs; handshake'dan token.


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • WebSocket 5.13-bob: Socket.io — NestJS gateway.
  • Cron/navbat (5.22, 8.22): scheduling, BullMQ.
  • Auth 8.9-bob: WS guard.
  • DTO/Guard (8.5, 8.6): WebSocket'da ham.
  • Redis 8.15-bob: adapter, lock.
  • Config 8.14-bob: cron jadval, vaqt zonasi.
  • GraphQL Sub 8.17-bob: real-time muqobil.
  • DevOps (10): masshtab (Redis adapter, lock).

8. Eng yaxshi amaliyotlar (best practices)

  • Socket.io (reconnect/room — 2.1); gateway yupqa service 8.1-bob.
  • Xonalar (maqsadli broadcast — 2.5); WS auth guard 2.8-bob.
  • Redis adapter (ko'p instance — 2.9); validatsiya (DTO — 2.6).
  • Lifecycle (online/offline — 2.4); WsException filter 2.7-bob.
  • Cron yengil (og'ir navbat — 2.14, 8.22).
  • timeZone; idempotent; distributed lock (ko'p instance — 2.14).
  • CronExpression enum (o'qiladi — 2.11); config'dan jadval 8.14-bob.
  • To'g'ri tur (@Cron/@Interval/@Timeout — 2.11, 2.12).
  • To'g'ri real-time vosita (WebSocket vs SSE — 2.15).
  • Dinamik vazifa (foydalanuvchi reja — 2.13).

9. Amaliy loyiha: "Real-time Chat + Scheduled Tizim"

NestJS WebSocket va cron'ni mustahkamlash (8-QISM yakuni).

Maqsad

Real-time chat (WebSocket) va avtomatik vazifalar (cron) tizimi: xona, auth, bildirishnoma, hisobot, eslatma.

Talablar (requirements)

  1. Chat gateway: xona, xabar, lifecycle (Misol 1, 2.2, 2.4, 2.5).
  2. WS auth: token guard (Misol 2, 2.8).
  3. Bildirishnoma: service gateway mijoz push (Misol 3, 2.3).
  4. DTO validatsiya: xabar (Misol 8, 2.6).
  5. WsException: xato filter 2.7-bob.
  6. Redis adapter: ko'p instance (Misol 9, 2.9).
  7. Cron: hisobot, tozalash, eslatma (Misol 4, 2.11).
  8. Distributed lock: ko'p instance (Misol 6, 2.14).
  9. Cron + navbat: og'ir ish (Misol 7, 8.22).
  10. Dinamik eslatma: runtime cron (Misol 5, 2.13).

Maslahatlar (hint)

  • WS auth: handshake token (2.8, 6-xato).
  • Redis adapter (ko'p instance — 2.9, 2-xato).
  • Cron lock (2.14, 4-xato).
  • timeZone (2.11, 5-xato).
  • Cron yengil navbat (2.14, 2-holat).
  • Xona broadcast (server.to — 2.5).

"Tayyor" mezonlari (acceptance criteria)

  • Chat gateway (xona, lifecycle).
  • WS auth (token).
  • Bildirishnoma (push).
  • DTO validatsiya.
  • WsException filter.
  • Redis adapter.
  • Cron (hisobot/tozalash).
  • Distributed lock.
  • Cron + navbat.
  • Dinamik eslatma.

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda real-time va avtomatlashtirishni chuqur o'rgandik:

  • WebSocket: asoslar (5.13 — 2.1); gateway (@WebSocketGateway/@SubscribeMessage — 2.2); socket/broadcast 2.3-bob; lifecycle 2.4-bob; xonalar 2.5-bob; validatsiya 2.6-bob; WsException 2.7-bob; auth 2.8-bob; Redis adapter (masshtab — 2.9).
  • Task scheduling: ScheduleModule 2.10-bob; @Cron 2.11-bob; @Interval/@Timeout 2.12-bob; dinamik 2.13-bob; production (lock, idempotent, navbat — 2.14).
  • Real-time tanlov (WebSocket vs SSE — 2.15); best practices 2.16-bob.

8-QISM (NestJS) TUGADI! Bu — kitobning eng katta, eng muhim bo'limi (18 bob): NestJS arxitektura, DI, DB (SQL+Mongo), DTO, guard/interceptor/filter, RBAC, auth, email, test, bot, config, cache/log/error, mikroservis, GraphQL, WebSocket, cron. Endi siz professional NestJS backend dasturchisisiz — har turdagi server ilovasini qura olasiz.

Keyingi bob — 9.1-bob: Dasturiy ta'minot arxitekturasi (9-QISM boshlanishi). Backend texnologiyalarini bildik; endi ularni to'g'ri tashkil qilish — arxitektura — ni o'rganamiz: SOLID, Clean Architecture, DDD, design patterns, monorepo. Bu — kodni katta, barqaror, jamoaviy qiladigan bilim (senior daraja).


Foydalanilgan rasmiy/ishonchli manbalar

  • docs.nestjs.com/websockets/gateways (@WebSocketGateway, @SubscribeMessage, rooms)
  • docs.nestjs.com/techniques/task-scheduling (@Cron, @Interval, @Timeout, SchedulerRegistry)
  • socket.io/docs (server API, rooms, namespaces, adapter)
  • socket.io/docs/v4/redis-adapter (ko'p instance sinxronizatsiyasi)

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
8.18-bob: WebSockets gateway va Task scheduling (cron) — Wisar