Kigezo cha kupakia (loading spinner) hakikupi taarifa yoyote. Wakati kazi ya AI inachukua dakika nyingi—au inarudi kwenye foleni kwa jaribio la tatu—unahitaji kuona hali ilivyo. Server-Sent Events inakupa uwezo huo wa kuona bila mzigo wa handshake wa WebSockets au ugumu wa long polling. Seva inafungua jibu moja la HTTP na kusukuma (push) masasisho ya maandishi rahisi (plain-text) wakati mambo yanapobadilika. Mteja (client) uyasoma yanapowasili.
Ikiwa muunganisho utakatika, pengine hutaki kuanza upya. Mtiririko (stream) wa SSE ulioundwa vizuri unakumbuka ulikuwa wapi. Kwa kutumia Node.js 20 na maktaba ya kawaida pekee, unaweza kuunganisha mfumo huu. Hakuna vifurushi vya nje vinavyohitajika.
Muundo wa data unavyoonekana
Ujumbe wa SSE ni maandishi rahisi. Seva huandika mambo matatu: jina la tukio (event name) la hiari, uwanja wa data unaohitajika, na uwanja wa id ambao unakuwa sehemu yako ya kuhifadhi (save point). Kila rekodi huishia na alama mbili za mstari mpya (newline characters)—mstari mtupu unaoweka mpaka.
Mtiririko mzuri unaweza kuonekana hivi kwenye mtandao:
id: 14
event: status
data: {"phase":"testing","progress":43}
id: 15
event: status
data: {"phase":"retrying","attempt":2}
Mteja wa EventSource wa kivinjari unasoma mistari hii kiotomatiki. Inazalisha tukio (event) kwa kila kifungu na kuhifadhi id ya mwisho ndani yake. Ikiwa muunganisho wa TCP utakatika, mteja unasubiri, unajaribu kuunganisha tena, na kutuma utambulisho uliohifadhiwa kwenye seva kama kichwa cha habari cha Last-Event-ID. Kichwa hicho cha habari ndicho sababu pekee inayofanya mfumo huu ufanye kazi. Bila hiyo, huna kielelezo (cursor) thabiti.
Kuunganisha seva kwenye Node.js
Moduli ya http iliyojengwa ndani ya Node inaweza kushughulikia hili moja kwa moja. Ombi linapokuja, weka vichwa vya habari (headers) sahihi ili mteja ajue kuwa huu ni mtiririko (stream), si ukurasa:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Ondoa mfumo wa kuhifadhi muda (buffering). Proksia na mifumo (frameworks) wakati mwingine huunganisha majibu (batch responses), jambo ambalo linaharibu hisia ya wakati halisi (real-time), hivyo safisha (flush) baada ya kila kipande (chunk).
Tuma ID kwanza, kisha aina ya tukio, kisha data ya ujumbe (payload), kisha mstari mtupu wa mwisho. Mpangilio ni muhimu tu kwa sababu ID lazima ifike kabla ya mstari mtupu ili mteja iweze kuikamata. Ikiwa unatumia response.write() ya asili, matokeo ni haya:
response.write(`id: ${cursor}\n`);
response.write(`event: ${eventName}\n`);
response.write(`data: ${JSON.stringify(payload)}\n\n`);
Hicho \n\n cha mwisho si kwa ajili ya urembo. Vichanganuzi (parsers) vya SSE huichukulia kama kiashiria cha mwisho wa rekodi. Ukikosa, mteja utabaki ukisubiri data zaidi.
Kielelezo (cursor) ndicho kila kitu
Muunganisho mpya wa HTTP hauhakikishii hali mpya. Mteja anapounganisha tena, kichwa cha habari cha Last-Event-ID kinakuambia ujumbe wa mwisho walioupokea. Kazi yako ni kuendelea kutoka ujumbe unaofuata, si kuanzia mwanzo.
Hii inamaanisha kudumisha kumbukumbu (log) au jarida la matukio upande wa seva. Array ya kwenye kumbukumbu (in-memory array) inafaa kwa onyesho (demo). Katika uzalishaji (production), unahitaji kitu thabiti—ongeza kwenye log ya kanzidata (database log), Redis stream, au jarida la kuandika kabla (write-ahead journal)—kwa sababu kuanzishwa upya kwa seva hakupaswi kufuta historia na kuwalazimisha wateja wote kuanza upya.
Weka index ya matukio yako kwa namba inayoongezeka (monotonically increasing integer) au ULID. Unganisho mpya unapokuja, uliza matukio ambapo id > lastEventId, na uyacheze upya kwa mpangilio. Weka ucheleweshaji mdogo wa bandia au uunganishe (batch) ikiwa una mamia ya ujumbe yaliyochelewa, lakini yawatumie kuanzia ya zamani zaidi ili mteja aweze kujenga hali kwa mtiririko wa muda.
Tarajia marudio (duplicates)
Mitandao si ya kuaminika. Seva inaweza kutuma tukio, ikapoteza uthibitisho wa TCP, na kulituma tena baada ya muda fulani kuisha. Sanifu mfumo kwa utoaji wa angalau mara moja (at-least-once delivery) tangu mwanzo.
Upande wa mteja, kuondoa marudio (deduplication) ni rahisi. Weka Map inayotumia ID ya tukio kama funguo. Tukio jipya linapowasili, kagua hiyo map. Ikiwa ID ipo, futa marudio hiyo kimyakimya. Kwa sababu seva yako inatoa ID zinazotabirika, hii inafanya marudio yasilete madhara. Map hiyo haihitaji kukua milele. Mara tu unapothibitisha kuwa tukio limechukuliwa salama, futa ID za zamani. Dirisha linaloteleza (sliding window) la aya mia chache kawaida linatosha kwa wateja wa kivinjari.
Kielelezo (cursor) kinapopita muda wake
Hatimaye mteja atatafuta kuunganisha tena baada ya saa au siku. Ikiwa kumbukumbu yako ya historia inahusu matukio elfu ya mwisho tu na mteja amepitwa na matukio elfu mbili, kucheza upya mapengo itakuwa haiwezekani.
Usitiririshe historia ya sehemu tu. Hiyo inamwacha mteja katika hali isiyo thabiti. Badala yake, tambua kielelezo kilichopitwa na wakati na utume snapshot kamili kama tukio linalofuata. Snapshot hiyo inapaswa kubeba kielelezo kipya kinachomfanya mteja aambatane na hali ya sasa. Kutoka hapo, mabadiliko ya moja kwa moja (live deltas) yanaendelea kama kawaida. Ainisha mpaka huu waziwazi katika itifaki (protocol) yako ili kodi ya mteja ijue wakati wa kuweka upya modeli yake ya ndani badala ya kuongeza tu.
Linda mtiririko
Njia za SSE (SSE endpoints) zilizo wazi ni malengo ya kuvutia. Mtu yeyote anaweza kushikilia muunganisho, na maombi ya kurudia yanaweza kuongeza mzigo wa kusoma kwenye hifadhi yako.
Gate the endpoint with proper authorization. Because the browser EventSource does not support custom headers, pass the token in the query string or use cookies with strict SameSite policies. Validate the token before you allocate stream resources.
Set history limits and per-user quotas. Cap the number of stored events per task, and cap the number of concurrent connections per client. Log disconnects and replays so you can spot a rogue client hammering your cursor endpoint.
The pattern travels
This approach is not trapped inside HTTP. The same rules apply when you move to WebSockets, message queues, or agent-to-agent interfaces. The transport changes—you might use binary frames or topic subscriptions—but the underlying problem stays identical. You need a cursor, a durable log, at-least-once semantics, client deduplication, and a fallback to full snapshots when the cursor goes stale. Solve state convergence once, and you can ship it over TCP, WebSocket, or a broker like RabbitMQ without redesigning the core logic.
Keep it simple
Server-Sent Events work because they ride on ordinary HTTP. Proxies understand them. Load balancers can health-check them. Debugging is as easy as curl. But that simplicity disappears if you ignore the edge cases. Build the cursor. Expect replays. Deduplicate on the client. snapshot when history runs out. Do that, and your long-running AI tasks will report their progress honestly, even through spotty Wi-Fi, server restarts, and the occasional overnight browser sleep.
Source: Build a Reconnecting SSE Task Stream with Node.js
Join the discussion: GyaanSetu AI Community
