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)
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 wsWebSocket 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)
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).
@Module({
providers: [ChatGateway, ChatService], // gateway — provider (controller EMAS)
})
export class ChatModule {}2.2b. @WebSocketGateway parametrlari (port, namespace, cors)
// 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'dagiio(server, options)sozlamalarining dekorator ko'rinishi.
2.2c. socket.io vs ws — adapter tanlash (IoAdapter / WsAdapter)
// 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 wsAdapter — 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) sofws: 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 holatdaws.RedisIoAdapter2.9-bob —IoAdapterni kengaytiradi (masshtab uchun).
2.3. Client va Socket (ulanish)
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)
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:
OnGatewayInitafterInit(server)(WebSocket server tayyor bo'lganda bir marta — global middleware/adapter sozlash);OnGatewayConnectionhandleConnection(client)(har mijoz ulanganda — auth, online belgilash — auth o'tmasaclient.disconnect());OnGatewayDisconnecthandleDisconnect(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)
@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
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)
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
errorhodisasi 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)
// 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 tekshiradiclient.data.user. WebSocket — HTTP cookie/header'dan farq (handshake). Auth — chat/real-time xavfsizligi (14).
2.9. Redis adapter (masshtab — ko'p instance)
// 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)
// 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)
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):
┌──────────── 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:30Ko'p ishlatiladigan CronExpression enum qiymatlari (raqam yodlamaslik uchun — o'qiladigan nom):
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
@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)
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):
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 uchunaddInterval/deleteInterval; timeout uchunaddTimeout/deleteTimeout(o'chirishda ularclearInterval/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)
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)
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 SSEReal-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)
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
// 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)
@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)
@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)
// 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)
@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)
@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)
// 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)
// 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)
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)
// 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
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 bildirishnoma5. To'g'ri va noto'g'ri holatlar
1) Ko'p instance cron locksiz
3 instance vazifa 3 marta (2.14)
distributed lock (Redis — bir marta)2) Og'ir ish cron ichida
cron'da og'ir hisobot (bloklaydi — 2.14)
navbatga (BullMQ — worker'da — 8.22)3) WebSocket auth'siz
kim ulansa, xabar (14)
WS guard (token — 2.8)4) Ko'p instance WebSocket Redis adapter'siz
mijozlar turli instance'da xabar yetmaydi (2.9)
Redis adapter5) Cron timeZone'siz
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)
- Chat gateway: xona, xabar, lifecycle (Misol 1, 2.2, 2.4, 2.5).
- WS auth: token guard (Misol 2, 2.8).
- Bildirishnoma: service gateway mijoz push (Misol 3, 2.3).
- DTO validatsiya: xabar (Misol 8, 2.6).
- WsException: xato filter 2.7-bob.
- Redis adapter: ko'p instance (Misol 9, 2.9).
- Cron: hisobot, tozalash, eslatma (Misol 4, 2.11).
- Distributed lock: ko'p instance (Misol 6, 2.14).
- Cron + navbat: og'ir ish (Misol 7, 8.22).
- 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!