[{"data":1,"prerenderedAt":2741},["ShallowReactive",2],{"post-\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events":3},{"id":4,"title":5,"body":6,"contentId":2726,"date":2727,"description":2728,"extension":2729,"meta":2730,"navigation":159,"path":2731,"rawbody":2732,"robots":2733,"schemaOrg":2733,"seo":2734,"sitemap":2735,"stem":2739,"__hash__":2740},"posts\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events.md","Create Real-Time Node.js Apps with Server-Sent Events",{"type":7,"value":8,"toc":2716},"minimark",[9,18,27,30,35,38,42,50,63,70,492,501,504,520,524,529,689,692,696,699,706,1008,1033,1037,1040,1043,1357,1366,1372,1610,1614,1617,1620,1627,1630,1633,2138,2148,2154,2163,2184,2194,2198,2203,2210,2213,2652,2661,2667,2673,2676,2682,2686,2689,2692,2706,2709,2712],[10,11,12,13,17],"p",{},"I'm deeply convinced that to understand a concept or a technology, we all need to try ",[14,15,16],"strong",{},"to cook something with it"," using the lowest level possible. This is not only related to programming but to everything in life.",[10,19,20,21,26],{},"In the continuation of the article ",[22,23,25],"a",{"href":24},"\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets","Real-time without WebSocket",", I think that building a little client-server application using Server-Sent Events (SSE) is a good way to understand how it works and to give us the confidence to use it in our projects.",[10,28,29],{},"This client-server application will be a little chat where the client will send a message to the server and the server will broadcast the message to all connected clients. No external library, no framework, just Node.js.",[31,32,34],"h2",{"id":33},"requirements","Requirements",[10,36,37],{},"To follow this tutorial, we need to have Node.js installed on our machine. That's all.",[31,39,41],{"id":40},"the-server","The server",[10,43,44,45,49],{},"Create a file named ",[46,47,48],"code",{},"server.mjs",". This file will contain the server code to perform the following actions:",[51,52,53,57,60],"ul",{},[54,55,56],"li",{},"Serve the client-side code aka a simple HTML file",[54,58,59],{},"Receive messages from clients",[54,61,62],{},"Accept Server-Sent Events connections and broadcast messages",[10,64,65,66,69],{},"But for now, let's begin by serving the client-side code. Our server will be built using the native Node.js ",[46,67,68],{},"http"," module.",[71,72,77],"pre",{"className":73,"code":74,"language":75,"meta":76,"style":76},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { readFileSync } from 'node:fs'\nimport { createServer } from 'node:http'\nimport { join } from 'node:path'\n\nconst hostname = '127.0.0.1'\nconst port = 3000\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Serve the index.html file\n  res.statusCode = 200\n  res.setHeader('Content-Type', 'text\u002Fhtml')\n\n  const htmlFile = join(process.cwd(), 'index.html')\n  const html = readFileSync(htmlFile, 'utf-8')\n\n  res.write(html)\n  res.end()\n})\n\nserver.listen(port, hostname, () => {\n  console.log(`Server running at http:\u002F\u002F${hostname}:${port}\u002F`)\n})\n","js","",[46,78,79,112,133,154,161,181,195,200,237,244,262,295,300,337,365,370,387,400,408,413,441,485],{"__ignoreMap":76},[80,81,84,88,92,96,99,102,105,109],"span",{"class":82,"line":83},"line",1,[80,85,87],{"class":86},"s7zQu","import",[80,89,91],{"class":90},"sMK4o"," {",[80,93,95],{"class":94},"sTEyZ"," readFileSync",[80,97,98],{"class":90}," }",[80,100,101],{"class":86}," from",[80,103,104],{"class":90}," '",[80,106,108],{"class":107},"sfazB","node:fs",[80,110,111],{"class":90},"'\n",[80,113,115,117,119,122,124,126,128,131],{"class":82,"line":114},2,[80,116,87],{"class":86},[80,118,91],{"class":90},[80,120,121],{"class":94}," createServer",[80,123,98],{"class":90},[80,125,101],{"class":86},[80,127,104],{"class":90},[80,129,130],{"class":107},"node:http",[80,132,111],{"class":90},[80,134,136,138,140,143,145,147,149,152],{"class":82,"line":135},3,[80,137,87],{"class":86},[80,139,91],{"class":90},[80,141,142],{"class":94}," join",[80,144,98],{"class":90},[80,146,101],{"class":86},[80,148,104],{"class":90},[80,150,151],{"class":107},"node:path",[80,153,111],{"class":90},[80,155,157],{"class":82,"line":156},4,[80,158,160],{"emptyLinePlaceholder":159},true,"\n",[80,162,164,168,171,174,176,179],{"class":82,"line":163},5,[80,165,167],{"class":166},"spNyl","const",[80,169,170],{"class":94}," hostname ",[80,172,173],{"class":90},"=",[80,175,104],{"class":90},[80,177,178],{"class":107},"127.0.0.1",[80,180,111],{"class":90},[80,182,184,186,189,191],{"class":82,"line":183},6,[80,185,167],{"class":166},[80,187,188],{"class":94}," port ",[80,190,173],{"class":90},[80,192,194],{"class":193},"sbssI"," 3000\n",[80,196,198],{"class":82,"line":197},7,[80,199,160],{"emptyLinePlaceholder":159},[80,201,203,205,208,210,213,216,218,222,225,228,231,234],{"class":82,"line":202},8,[80,204,167],{"class":166},[80,206,207],{"class":94}," server ",[80,209,173],{"class":90},[80,211,121],{"class":212},"s2Zo4",[80,214,215],{"class":94},"(",[80,217,215],{"class":90},[80,219,221],{"class":220},"sHdIc","req",[80,223,224],{"class":90},",",[80,226,227],{"class":220}," res",[80,229,230],{"class":90},")",[80,232,233],{"class":166}," =>",[80,235,236],{"class":90}," {\n",[80,238,240],{"class":82,"line":239},9,[80,241,243],{"class":242},"sHwdD","  \u002F\u002F Serve the index.html file\n",[80,245,247,250,253,256,259],{"class":82,"line":246},10,[80,248,249],{"class":94},"  res",[80,251,252],{"class":90},".",[80,254,255],{"class":94},"statusCode",[80,257,258],{"class":90}," =",[80,260,261],{"class":193}," 200\n",[80,263,265,267,269,272,275,278,281,283,285,287,290,292],{"class":82,"line":264},11,[80,266,249],{"class":94},[80,268,252],{"class":90},[80,270,271],{"class":212},"setHeader",[80,273,215],{"class":274},"swJcz",[80,276,277],{"class":90},"'",[80,279,280],{"class":107},"Content-Type",[80,282,277],{"class":90},[80,284,224],{"class":90},[80,286,104],{"class":90},[80,288,289],{"class":107},"text\u002Fhtml",[80,291,277],{"class":90},[80,293,294],{"class":274},")\n",[80,296,298],{"class":82,"line":297},12,[80,299,160],{"emptyLinePlaceholder":159},[80,301,303,306,309,311,313,315,318,320,323,326,328,330,333,335],{"class":82,"line":302},13,[80,304,305],{"class":166},"  const",[80,307,308],{"class":94}," htmlFile",[80,310,258],{"class":90},[80,312,142],{"class":212},[80,314,215],{"class":274},[80,316,317],{"class":94},"process",[80,319,252],{"class":90},[80,321,322],{"class":212},"cwd",[80,324,325],{"class":274},"()",[80,327,224],{"class":90},[80,329,104],{"class":90},[80,331,332],{"class":107},"index.html",[80,334,277],{"class":90},[80,336,294],{"class":274},[80,338,340,342,345,347,349,351,354,356,358,361,363],{"class":82,"line":339},14,[80,341,305],{"class":166},[80,343,344],{"class":94}," html",[80,346,258],{"class":90},[80,348,95],{"class":212},[80,350,215],{"class":274},[80,352,353],{"class":94},"htmlFile",[80,355,224],{"class":90},[80,357,104],{"class":90},[80,359,360],{"class":107},"utf-8",[80,362,277],{"class":90},[80,364,294],{"class":274},[80,366,368],{"class":82,"line":367},15,[80,369,160],{"emptyLinePlaceholder":159},[80,371,373,375,377,380,382,385],{"class":82,"line":372},16,[80,374,249],{"class":94},[80,376,252],{"class":90},[80,378,379],{"class":212},"write",[80,381,215],{"class":274},[80,383,384],{"class":94},"html",[80,386,294],{"class":274},[80,388,390,392,394,397],{"class":82,"line":389},17,[80,391,249],{"class":94},[80,393,252],{"class":90},[80,395,396],{"class":212},"end",[80,398,399],{"class":274},"()\n",[80,401,403,406],{"class":82,"line":402},18,[80,404,405],{"class":90},"}",[80,407,294],{"class":94},[80,409,411],{"class":82,"line":410},19,[80,412,160],{"emptyLinePlaceholder":159},[80,414,416,419,421,424,427,429,432,434,437,439],{"class":82,"line":415},20,[80,417,418],{"class":94},"server",[80,420,252],{"class":90},[80,422,423],{"class":212},"listen",[80,425,426],{"class":94},"(port",[80,428,224],{"class":90},[80,430,431],{"class":94}," hostname",[80,433,224],{"class":90},[80,435,436],{"class":90}," ()",[80,438,233],{"class":166},[80,440,236],{"class":90},[80,442,444,447,449,452,454,457,460,463,466,468,471,473,476,478,481,483],{"class":82,"line":443},21,[80,445,446],{"class":94},"  console",[80,448,252],{"class":90},[80,450,451],{"class":212},"log",[80,453,215],{"class":274},[80,455,456],{"class":90},"`",[80,458,459],{"class":107},"Server running at http:\u002F\u002F",[80,461,462],{"class":90},"${",[80,464,465],{"class":94},"hostname",[80,467,405],{"class":90},[80,469,470],{"class":107},":",[80,472,462],{"class":90},[80,474,475],{"class":94},"port",[80,477,405],{"class":90},[80,479,480],{"class":107},"\u002F",[80,482,456],{"class":90},[80,484,294],{"class":274},[80,486,488,490],{"class":82,"line":487},22,[80,489,405],{"class":90},[80,491,294],{"class":94},[10,493,494,495,497,498,252],{},"When a request hits the server, it will respond with the content of the ",[46,496,332],{}," file by reading it on the disk. The server is listening on the port ",[46,499,500],{},"3000",[10,502,503],{},"We can start our server with the following command:",[71,505,509],{"className":506,"code":507,"language":508,"meta":76,"style":76},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","node server.mjs\n","sh",[46,510,511],{"__ignoreMap":76},[80,512,513,517],{"class":82,"line":83},[80,514,516],{"class":515},"sBMFI","node",[80,518,519],{"class":107}," server.mjs\n",[31,521,523],{"id":522},"the-client","The client",[10,525,44,526,528],{},[46,527,332],{}," at the root of the project. This file will contain the client-side code:",[71,530,533],{"className":531,"code":532,"language":384,"meta":76,"style":76},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[46,534,535,548,570,580,603,635,655,664,673,681],{"__ignoreMap":76},[80,536,537,540,543,545],{"class":82,"line":83},[80,538,539],{"class":90},"\u003C!",[80,541,542],{"class":274},"doctype",[80,544,344],{"class":166},[80,546,547],{"class":90},">\n",[80,549,550,553,555,558,560,563,566,568],{"class":82,"line":114},[80,551,552],{"class":90},"\u003C",[80,554,384],{"class":274},[80,556,557],{"class":166}," lang",[80,559,173],{"class":90},[80,561,562],{"class":90},"\"",[80,564,565],{"class":107},"en",[80,567,562],{"class":90},[80,569,547],{"class":90},[80,571,572,575,578],{"class":82,"line":135},[80,573,574],{"class":90},"  \u003C",[80,576,577],{"class":274},"head",[80,579,547],{"class":90},[80,581,582,585,588,591,593,595,598,600],{"class":82,"line":156},[80,583,584],{"class":90},"    \u003C",[80,586,587],{"class":274},"meta",[80,589,590],{"class":166}," charset",[80,592,173],{"class":90},[80,594,562],{"class":90},[80,596,597],{"class":107},"UTF-8",[80,599,562],{"class":90},[80,601,602],{"class":90}," \u002F>\n",[80,604,605,607,609,612,614,616,619,621,624,626,628,631,633],{"class":82,"line":163},[80,606,584],{"class":90},[80,608,587],{"class":274},[80,610,611],{"class":166}," name",[80,613,173],{"class":90},[80,615,562],{"class":90},[80,617,618],{"class":107},"viewport",[80,620,562],{"class":90},[80,622,623],{"class":166}," content",[80,625,173],{"class":90},[80,627,562],{"class":90},[80,629,630],{"class":107},"width=device-width, initial-scale=1.0",[80,632,562],{"class":90},[80,634,602],{"class":90},[80,636,637,639,642,645,648,651,653],{"class":82,"line":183},[80,638,584],{"class":90},[80,640,641],{"class":274},"title",[80,643,644],{"class":90},">",[80,646,647],{"class":94},"Node SSE",[80,649,650],{"class":90},"\u003C\u002F",[80,652,641],{"class":274},[80,654,547],{"class":90},[80,656,657,660,662],{"class":82,"line":197},[80,658,659],{"class":90},"  \u003C\u002F",[80,661,577],{"class":274},[80,663,547],{"class":90},[80,665,666,668,671],{"class":82,"line":202},[80,667,574],{"class":90},[80,669,670],{"class":274},"body",[80,672,547],{"class":90},[80,674,675,677,679],{"class":82,"line":239},[80,676,659],{"class":90},[80,678,670],{"class":274},[80,680,547],{"class":90},[80,682,683,685,687],{"class":82,"line":246},[80,684,650],{"class":90},[80,686,384],{"class":274},[80,688,547],{"class":90},[10,690,691],{},"Yeah, that's a very simple HTML file, nothing fancy for now.",[31,693,695],{"id":694},"server-sent-events-connection","Server-sent events connection",[10,697,698],{},"Now, let's add the Server-Sent Events connection to the server. To handle SSE, we need to respond with some headers to keep the connection open and to send data. So the client will ask for a resource and the server will respond with a stream.",[10,700,701,702,705],{},"This resource will be ",[46,703,704],{},"\u002Fevents",". Let's update the server code:",[71,707,709],{"className":73,"code":708,"language":75,"meta":76,"style":76},"\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n  \u002F\u002F `\u002Fevents` endpoint used to listen for messages\n  if (req.url === '\u002Fevents') {\n    res.setHeader('Content-Type', 'text\u002Fevent-stream')\n    res.setHeader('Cache-Control', 'no-cache')\n\n    \u002F\u002F TODO: Handle the connection\n  }\n  else {\n    \u002F\u002F Serve the index.html file\n    res.statusCode = 200\n    res.setHeader('Content-Type', 'text\u002Fhtml')\n\n    const htmlFile = join(process.cwd(), 'index.html')\n    const html = readFileSync(htmlFile, 'utf-8')\n\n    res.write(html)\n    res.end()\n  }\n})\n\n\u002F\u002F ...\n",[46,710,711,716,720,746,751,781,809,837,841,846,851,858,863,875,901,905,936,960,964,978,988,992,998,1003],{"__ignoreMap":76},[80,712,713],{"class":82,"line":83},[80,714,715],{"class":242},"\u002F\u002F ...\n",[80,717,718],{"class":82,"line":114},[80,719,160],{"emptyLinePlaceholder":159},[80,721,722,724,726,728,730,732,734,736,738,740,742,744],{"class":82,"line":135},[80,723,167],{"class":166},[80,725,207],{"class":94},[80,727,173],{"class":90},[80,729,121],{"class":212},[80,731,215],{"class":94},[80,733,215],{"class":90},[80,735,221],{"class":220},[80,737,224],{"class":90},[80,739,227],{"class":220},[80,741,230],{"class":90},[80,743,233],{"class":166},[80,745,236],{"class":90},[80,747,748],{"class":82,"line":156},[80,749,750],{"class":242},"  \u002F\u002F `\u002Fevents` endpoint used to listen for messages\n",[80,752,753,756,759,761,763,766,769,771,773,775,778],{"class":82,"line":163},[80,754,755],{"class":86},"  if",[80,757,758],{"class":274}," (",[80,760,221],{"class":94},[80,762,252],{"class":90},[80,764,765],{"class":94},"url",[80,767,768],{"class":90}," ===",[80,770,104],{"class":90},[80,772,704],{"class":107},[80,774,277],{"class":90},[80,776,777],{"class":274},") ",[80,779,780],{"class":90},"{\n",[80,782,783,786,788,790,792,794,796,798,800,802,805,807],{"class":82,"line":183},[80,784,785],{"class":94},"    res",[80,787,252],{"class":90},[80,789,271],{"class":212},[80,791,215],{"class":274},[80,793,277],{"class":90},[80,795,280],{"class":107},[80,797,277],{"class":90},[80,799,224],{"class":90},[80,801,104],{"class":90},[80,803,804],{"class":107},"text\u002Fevent-stream",[80,806,277],{"class":90},[80,808,294],{"class":274},[80,810,811,813,815,817,819,821,824,826,828,830,833,835],{"class":82,"line":197},[80,812,785],{"class":94},[80,814,252],{"class":90},[80,816,271],{"class":212},[80,818,215],{"class":274},[80,820,277],{"class":90},[80,822,823],{"class":107},"Cache-Control",[80,825,277],{"class":90},[80,827,224],{"class":90},[80,829,104],{"class":90},[80,831,832],{"class":107},"no-cache",[80,834,277],{"class":90},[80,836,294],{"class":274},[80,838,839],{"class":82,"line":202},[80,840,160],{"emptyLinePlaceholder":159},[80,842,843],{"class":82,"line":239},[80,844,845],{"class":242},"    \u002F\u002F TODO: Handle the connection\n",[80,847,848],{"class":82,"line":246},[80,849,850],{"class":90},"  }\n",[80,852,853,856],{"class":82,"line":264},[80,854,855],{"class":86},"  else",[80,857,236],{"class":90},[80,859,860],{"class":82,"line":297},[80,861,862],{"class":242},"    \u002F\u002F Serve the index.html file\n",[80,864,865,867,869,871,873],{"class":82,"line":302},[80,866,785],{"class":94},[80,868,252],{"class":90},[80,870,255],{"class":94},[80,872,258],{"class":90},[80,874,261],{"class":193},[80,876,877,879,881,883,885,887,889,891,893,895,897,899],{"class":82,"line":339},[80,878,785],{"class":94},[80,880,252],{"class":90},[80,882,271],{"class":212},[80,884,215],{"class":274},[80,886,277],{"class":90},[80,888,280],{"class":107},[80,890,277],{"class":90},[80,892,224],{"class":90},[80,894,104],{"class":90},[80,896,289],{"class":107},[80,898,277],{"class":90},[80,900,294],{"class":274},[80,902,903],{"class":82,"line":367},[80,904,160],{"emptyLinePlaceholder":159},[80,906,907,910,912,914,916,918,920,922,924,926,928,930,932,934],{"class":82,"line":372},[80,908,909],{"class":166},"    const",[80,911,308],{"class":94},[80,913,258],{"class":90},[80,915,142],{"class":212},[80,917,215],{"class":274},[80,919,317],{"class":94},[80,921,252],{"class":90},[80,923,322],{"class":212},[80,925,325],{"class":274},[80,927,224],{"class":90},[80,929,104],{"class":90},[80,931,332],{"class":107},[80,933,277],{"class":90},[80,935,294],{"class":274},[80,937,938,940,942,944,946,948,950,952,954,956,958],{"class":82,"line":389},[80,939,909],{"class":166},[80,941,344],{"class":94},[80,943,258],{"class":90},[80,945,95],{"class":212},[80,947,215],{"class":274},[80,949,353],{"class":94},[80,951,224],{"class":90},[80,953,104],{"class":90},[80,955,360],{"class":107},[80,957,277],{"class":90},[80,959,294],{"class":274},[80,961,962],{"class":82,"line":402},[80,963,160],{"emptyLinePlaceholder":159},[80,965,966,968,970,972,974,976],{"class":82,"line":410},[80,967,785],{"class":94},[80,969,252],{"class":90},[80,971,379],{"class":212},[80,973,215],{"class":274},[80,975,384],{"class":94},[80,977,294],{"class":274},[80,979,980,982,984,986],{"class":82,"line":415},[80,981,785],{"class":94},[80,983,252],{"class":90},[80,985,396],{"class":212},[80,987,399],{"class":274},[80,989,990],{"class":82,"line":443},[80,991,850],{"class":90},[80,993,994,996],{"class":82,"line":487},[80,995,405],{"class":90},[80,997,294],{"class":94},[80,999,1001],{"class":82,"line":1000},23,[80,1002,160],{"emptyLinePlaceholder":159},[80,1004,1006],{"class":82,"line":1005},24,[80,1007,715],{"class":242},[10,1009,1010,1011,1013,1014,1017,1018,1021,1022,1025,1026,1029,1030,1032],{},"When the client asks for the resource ",[46,1012,704],{},", the server will respond with the headers ",[46,1015,1016],{},"Content-Type: text\u002Fevent-stream",", and ",[46,1019,1020],{},"Cache-Control: no-cache",". This will tell the client that the connection is a stream, i.e. the server will send data to the client at any time. The connection must not be cached to avoid issues with the stream.\nIt's important to note that we never call the function ",[46,1023,1024],{},"res.end()",". This is because ",[14,1027,1028],{},"the connection must stay open"," to allow the server to send data to the client. The function ",[46,1031,1024],{}," closes the connection.",[31,1034,1036],{"id":1035},"chat-logic","Chat logic",[10,1038,1039],{},"This is very simple and simplified to the maximum, but the logic behind remains the same as a more complex application.",[10,1041,1042],{},"In our server, we will have a logic to receive messages from clients.",[71,1044,1046],{"className":73,"code":1045,"language":75,"meta":76,"style":76},"\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n\u002F\u002F `\u002Fevents` endpoint used to listen for messages\n  if (req.url === '\u002Fevents') {\n    \u002F\u002F ...\n\n    \u002F\u002F `\u002Fmessages` endpoint used to post messages\n  }\n  else if (req.url === '\u002Fmessage') {\n    let body = ''\n\n    req.on('data', (chunk) => {\n      body += chunk\n    })\n\n    req.on('end', () => {\n      \u002F\u002F TODO: broadcast the message\n\n      \u002F\u002F Redirect to the index.html file\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Serve the index.html file\n    \u002F\u002F ...\n  }\n})\n",[46,1047,1048,1052,1056,1082,1087,1111,1116,1120,1125,1129,1157,1170,1174,1206,1217,1224,1228,1252,1257,1261,1266,1280,1307,1317,1323,1328,1335,1340,1345,1350],{"__ignoreMap":76},[80,1049,1050],{"class":82,"line":83},[80,1051,715],{"class":242},[80,1053,1054],{"class":82,"line":114},[80,1055,160],{"emptyLinePlaceholder":159},[80,1057,1058,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078,1080],{"class":82,"line":135},[80,1059,167],{"class":166},[80,1061,207],{"class":94},[80,1063,173],{"class":90},[80,1065,121],{"class":212},[80,1067,215],{"class":94},[80,1069,215],{"class":90},[80,1071,221],{"class":220},[80,1073,224],{"class":90},[80,1075,227],{"class":220},[80,1077,230],{"class":90},[80,1079,233],{"class":166},[80,1081,236],{"class":90},[80,1083,1084],{"class":82,"line":156},[80,1085,1086],{"class":242},"\u002F\u002F `\u002Fevents` endpoint used to listen for messages\n",[80,1088,1089,1091,1093,1095,1097,1099,1101,1103,1105,1107,1109],{"class":82,"line":163},[80,1090,755],{"class":86},[80,1092,758],{"class":274},[80,1094,221],{"class":94},[80,1096,252],{"class":90},[80,1098,765],{"class":94},[80,1100,768],{"class":90},[80,1102,104],{"class":90},[80,1104,704],{"class":107},[80,1106,277],{"class":90},[80,1108,777],{"class":274},[80,1110,780],{"class":90},[80,1112,1113],{"class":82,"line":183},[80,1114,1115],{"class":242},"    \u002F\u002F ...\n",[80,1117,1118],{"class":82,"line":197},[80,1119,160],{"emptyLinePlaceholder":159},[80,1121,1122],{"class":82,"line":202},[80,1123,1124],{"class":242},"    \u002F\u002F `\u002Fmessages` endpoint used to post messages\n",[80,1126,1127],{"class":82,"line":239},[80,1128,850],{"class":90},[80,1130,1131,1133,1136,1138,1140,1142,1144,1146,1148,1151,1153,1155],{"class":82,"line":246},[80,1132,855],{"class":86},[80,1134,1135],{"class":86}," if",[80,1137,758],{"class":274},[80,1139,221],{"class":94},[80,1141,252],{"class":90},[80,1143,765],{"class":94},[80,1145,768],{"class":90},[80,1147,104],{"class":90},[80,1149,1150],{"class":107},"\u002Fmessage",[80,1152,277],{"class":90},[80,1154,777],{"class":274},[80,1156,780],{"class":90},[80,1158,1159,1162,1165,1167],{"class":82,"line":264},[80,1160,1161],{"class":166},"    let",[80,1163,1164],{"class":94}," body",[80,1166,258],{"class":90},[80,1168,1169],{"class":90}," ''\n",[80,1171,1172],{"class":82,"line":297},[80,1173,160],{"emptyLinePlaceholder":159},[80,1175,1176,1179,1181,1184,1186,1188,1191,1193,1195,1197,1200,1202,1204],{"class":82,"line":302},[80,1177,1178],{"class":94},"    req",[80,1180,252],{"class":90},[80,1182,1183],{"class":212},"on",[80,1185,215],{"class":274},[80,1187,277],{"class":90},[80,1189,1190],{"class":107},"data",[80,1192,277],{"class":90},[80,1194,224],{"class":90},[80,1196,758],{"class":90},[80,1198,1199],{"class":220},"chunk",[80,1201,230],{"class":90},[80,1203,233],{"class":166},[80,1205,236],{"class":90},[80,1207,1208,1211,1214],{"class":82,"line":339},[80,1209,1210],{"class":94},"      body",[80,1212,1213],{"class":90}," +=",[80,1215,1216],{"class":94}," chunk\n",[80,1218,1219,1222],{"class":82,"line":367},[80,1220,1221],{"class":90},"    }",[80,1223,294],{"class":274},[80,1225,1226],{"class":82,"line":372},[80,1227,160],{"emptyLinePlaceholder":159},[80,1229,1230,1232,1234,1236,1238,1240,1242,1244,1246,1248,1250],{"class":82,"line":389},[80,1231,1178],{"class":94},[80,1233,252],{"class":90},[80,1235,1183],{"class":212},[80,1237,215],{"class":274},[80,1239,277],{"class":90},[80,1241,396],{"class":107},[80,1243,277],{"class":90},[80,1245,224],{"class":90},[80,1247,436],{"class":90},[80,1249,233],{"class":166},[80,1251,236],{"class":90},[80,1253,1254],{"class":82,"line":402},[80,1255,1256],{"class":242},"      \u002F\u002F TODO: broadcast the message\n",[80,1258,1259],{"class":82,"line":410},[80,1260,160],{"emptyLinePlaceholder":159},[80,1262,1263],{"class":82,"line":415},[80,1264,1265],{"class":242},"      \u002F\u002F Redirect to the index.html file\n",[80,1267,1268,1271,1273,1275,1277],{"class":82,"line":443},[80,1269,1270],{"class":94},"      res",[80,1272,252],{"class":90},[80,1274,255],{"class":94},[80,1276,258],{"class":90},[80,1278,1279],{"class":193}," 302\n",[80,1281,1282,1284,1286,1288,1290,1292,1295,1297,1299,1301,1303,1305],{"class":82,"line":487},[80,1283,1270],{"class":94},[80,1285,252],{"class":90},[80,1287,271],{"class":212},[80,1289,215],{"class":274},[80,1291,277],{"class":90},[80,1293,1294],{"class":107},"Location",[80,1296,277],{"class":90},[80,1298,224],{"class":90},[80,1300,104],{"class":90},[80,1302,480],{"class":107},[80,1304,277],{"class":90},[80,1306,294],{"class":274},[80,1308,1309,1311,1313,1315],{"class":82,"line":1000},[80,1310,1270],{"class":94},[80,1312,252],{"class":90},[80,1314,396],{"class":212},[80,1316,399],{"class":274},[80,1318,1319,1321],{"class":82,"line":1005},[80,1320,1221],{"class":90},[80,1322,294],{"class":274},[80,1324,1326],{"class":82,"line":1325},25,[80,1327,850],{"class":90},[80,1329,1331,1333],{"class":82,"line":1330},26,[80,1332,855],{"class":86},[80,1334,236],{"class":90},[80,1336,1338],{"class":82,"line":1337},27,[80,1339,862],{"class":242},[80,1341,1343],{"class":82,"line":1342},28,[80,1344,1115],{"class":242},[80,1346,1348],{"class":82,"line":1347},29,[80,1349,850],{"class":90},[80,1351,1353,1355],{"class":82,"line":1352},30,[80,1354,405],{"class":90},[80,1356,294],{"class":94},[10,1358,1359,1360,1362,1363,1365],{},"When the URL requested is ",[46,1361,1150],{},", the server will listen for the data sent by the client, which is the body of the request. When the request is finished, the server redirects the client to the ",[46,1364,332],{}," file. This is a classic behavior when a form is submitted for a server-rendered application.",[10,1367,1368,1369,1371],{},"In our ",[46,1370,332],{}," file, add a form to send messages to the server:",[71,1373,1375],{"className":531,"code":1374,"language":384,"meta":76,"style":76},"\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cform action=\"\u002Fmessage\" method=\"post\">\n      \u003Cinput type=\"text\" name=\"message\" \u002F>\n      \u003Cbutton type=\"submit\">Send\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[46,1376,1377,1387,1405,1413,1431,1459,1475,1483,1491,1523,1556,1585,1594,1602],{"__ignoreMap":76},[80,1378,1379,1381,1383,1385],{"class":82,"line":83},[80,1380,539],{"class":90},[80,1382,542],{"class":274},[80,1384,344],{"class":166},[80,1386,547],{"class":90},[80,1388,1389,1391,1393,1395,1397,1399,1401,1403],{"class":82,"line":114},[80,1390,552],{"class":90},[80,1392,384],{"class":274},[80,1394,557],{"class":166},[80,1396,173],{"class":90},[80,1398,562],{"class":90},[80,1400,565],{"class":107},[80,1402,562],{"class":90},[80,1404,547],{"class":90},[80,1406,1407,1409,1411],{"class":82,"line":135},[80,1408,574],{"class":90},[80,1410,577],{"class":274},[80,1412,547],{"class":90},[80,1414,1415,1417,1419,1421,1423,1425,1427,1429],{"class":82,"line":156},[80,1416,584],{"class":90},[80,1418,587],{"class":274},[80,1420,590],{"class":166},[80,1422,173],{"class":90},[80,1424,562],{"class":90},[80,1426,597],{"class":107},[80,1428,562],{"class":90},[80,1430,602],{"class":90},[80,1432,1433,1435,1437,1439,1441,1443,1445,1447,1449,1451,1453,1455,1457],{"class":82,"line":163},[80,1434,584],{"class":90},[80,1436,587],{"class":274},[80,1438,611],{"class":166},[80,1440,173],{"class":90},[80,1442,562],{"class":90},[80,1444,618],{"class":107},[80,1446,562],{"class":90},[80,1448,623],{"class":166},[80,1450,173],{"class":90},[80,1452,562],{"class":90},[80,1454,630],{"class":107},[80,1456,562],{"class":90},[80,1458,602],{"class":90},[80,1460,1461,1463,1465,1467,1469,1471,1473],{"class":82,"line":183},[80,1462,584],{"class":90},[80,1464,641],{"class":274},[80,1466,644],{"class":90},[80,1468,647],{"class":94},[80,1470,650],{"class":90},[80,1472,641],{"class":274},[80,1474,547],{"class":90},[80,1476,1477,1479,1481],{"class":82,"line":197},[80,1478,659],{"class":90},[80,1480,577],{"class":274},[80,1482,547],{"class":90},[80,1484,1485,1487,1489],{"class":82,"line":202},[80,1486,574],{"class":90},[80,1488,670],{"class":274},[80,1490,547],{"class":90},[80,1492,1493,1495,1498,1501,1503,1505,1507,1509,1512,1514,1516,1519,1521],{"class":82,"line":239},[80,1494,584],{"class":90},[80,1496,1497],{"class":274},"form",[80,1499,1500],{"class":166}," action",[80,1502,173],{"class":90},[80,1504,562],{"class":90},[80,1506,1150],{"class":107},[80,1508,562],{"class":90},[80,1510,1511],{"class":166}," method",[80,1513,173],{"class":90},[80,1515,562],{"class":90},[80,1517,1518],{"class":107},"post",[80,1520,562],{"class":90},[80,1522,547],{"class":90},[80,1524,1525,1528,1531,1534,1536,1538,1541,1543,1545,1547,1549,1552,1554],{"class":82,"line":246},[80,1526,1527],{"class":90},"      \u003C",[80,1529,1530],{"class":274},"input",[80,1532,1533],{"class":166}," type",[80,1535,173],{"class":90},[80,1537,562],{"class":90},[80,1539,1540],{"class":107},"text",[80,1542,562],{"class":90},[80,1544,611],{"class":166},[80,1546,173],{"class":90},[80,1548,562],{"class":90},[80,1550,1551],{"class":107},"message",[80,1553,562],{"class":90},[80,1555,602],{"class":90},[80,1557,1558,1560,1563,1565,1567,1569,1572,1574,1576,1579,1581,1583],{"class":82,"line":264},[80,1559,1527],{"class":90},[80,1561,1562],{"class":274},"button",[80,1564,1533],{"class":166},[80,1566,173],{"class":90},[80,1568,562],{"class":90},[80,1570,1571],{"class":107},"submit",[80,1573,562],{"class":90},[80,1575,644],{"class":90},[80,1577,1578],{"class":94},"Send",[80,1580,650],{"class":90},[80,1582,1562],{"class":274},[80,1584,547],{"class":90},[80,1586,1587,1590,1592],{"class":82,"line":297},[80,1588,1589],{"class":90},"    \u003C\u002F",[80,1591,1497],{"class":274},[80,1593,547],{"class":90},[80,1595,1596,1598,1600],{"class":82,"line":302},[80,1597,659],{"class":90},[80,1599,670],{"class":274},[80,1601,547],{"class":90},[80,1603,1604,1606,1608],{"class":82,"line":339},[80,1605,650],{"class":90},[80,1607,384],{"class":274},[80,1609,547],{"class":90},[31,1611,1613],{"id":1612},"broadcast-the-message","Broadcast the message",[10,1615,1616],{},"The missing feature is the broadcast of the message. How do we broadcast the message and what does it mean?",[10,1618,1619],{},"A broadcast is when a message is sent to everyone. Otherwise, it's a unicast, when a message is sent to a specific person.",[10,1621,1622],{},[1623,1624],"img",{"alt":1625,"src":1626},"Broadcast vs Unicast","\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fbroadcast-vs-unicast.png",[10,1628,1629],{},"In our case, the client A will send a message to the server (unicast). Then, the server will send this message to every client (broadcast).",[10,1631,1632],{},"The unicast has been implemented in the previous section. For the broadcast, we need to keep track of the connected clients to loop over them when a message is received.",[71,1634,1636],{"className":73,"code":1635,"language":75,"meta":76,"style":76},"\u002F\u002F ...\n\n\u002F\u002F Keep track of the connected clients\nconst connection = new Set()\n\nconst server = createServer((req, res) => {\n  \u002F\u002F `\u002Fevents` endpoint used to listen for messages\n  if (req.url === '\u002Fevents') {\n    res.setHeader('Content-Type', 'text\u002Fevent-stream')\n    res.setHeader('Cache-Control', 'no-cache')\n\n    connection.add(res)\n\n    req.on('close', () => {\n      connection.delete(res)\n    })\n\n    \u002F\u002F `\u002Fmessages` endpoint used to post messages\n  }\n  else if (req.url === '\u002Fmessage') {\n    let body = ''\n\n    req.on('data', (chunk) => {\n      body += chunk\n    })\n\n    req.on('end', () => {\n      \u002F\u002F Broadcast the message to all connected clients\n      for (const client of connection)\n        client.write(`data: ${body}\\n\\n`)\n\n      \u002F\u002F Redirect to the index.html file\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Serve the index.html file\n    \u002F\u002F ...\n  }\n})\n\n\u002F\u002F ...\n",[46,1637,1638,1642,1646,1651,1668,1672,1698,1702,1726,1752,1778,1782,1799,1803,1828,1844,1850,1854,1858,1862,1888,1898,1902,1930,1938,1944,1948,1972,1977,1997,2026,2031,2036,2049,2076,2087,2094,2099,2106,2111,2116,2121,2128,2133],{"__ignoreMap":76},[80,1639,1640],{"class":82,"line":83},[80,1641,715],{"class":242},[80,1643,1644],{"class":82,"line":114},[80,1645,160],{"emptyLinePlaceholder":159},[80,1647,1648],{"class":82,"line":135},[80,1649,1650],{"class":242},"\u002F\u002F Keep track of the connected clients\n",[80,1652,1653,1655,1658,1660,1663,1666],{"class":82,"line":156},[80,1654,167],{"class":166},[80,1656,1657],{"class":94}," connection ",[80,1659,173],{"class":90},[80,1661,1662],{"class":90}," new",[80,1664,1665],{"class":212}," Set",[80,1667,399],{"class":94},[80,1669,1670],{"class":82,"line":163},[80,1671,160],{"emptyLinePlaceholder":159},[80,1673,1674,1676,1678,1680,1682,1684,1686,1688,1690,1692,1694,1696],{"class":82,"line":183},[80,1675,167],{"class":166},[80,1677,207],{"class":94},[80,1679,173],{"class":90},[80,1681,121],{"class":212},[80,1683,215],{"class":94},[80,1685,215],{"class":90},[80,1687,221],{"class":220},[80,1689,224],{"class":90},[80,1691,227],{"class":220},[80,1693,230],{"class":90},[80,1695,233],{"class":166},[80,1697,236],{"class":90},[80,1699,1700],{"class":82,"line":197},[80,1701,750],{"class":242},[80,1703,1704,1706,1708,1710,1712,1714,1716,1718,1720,1722,1724],{"class":82,"line":202},[80,1705,755],{"class":86},[80,1707,758],{"class":274},[80,1709,221],{"class":94},[80,1711,252],{"class":90},[80,1713,765],{"class":94},[80,1715,768],{"class":90},[80,1717,104],{"class":90},[80,1719,704],{"class":107},[80,1721,277],{"class":90},[80,1723,777],{"class":274},[80,1725,780],{"class":90},[80,1727,1728,1730,1732,1734,1736,1738,1740,1742,1744,1746,1748,1750],{"class":82,"line":239},[80,1729,785],{"class":94},[80,1731,252],{"class":90},[80,1733,271],{"class":212},[80,1735,215],{"class":274},[80,1737,277],{"class":90},[80,1739,280],{"class":107},[80,1741,277],{"class":90},[80,1743,224],{"class":90},[80,1745,104],{"class":90},[80,1747,804],{"class":107},[80,1749,277],{"class":90},[80,1751,294],{"class":274},[80,1753,1754,1756,1758,1760,1762,1764,1766,1768,1770,1772,1774,1776],{"class":82,"line":246},[80,1755,785],{"class":94},[80,1757,252],{"class":90},[80,1759,271],{"class":212},[80,1761,215],{"class":274},[80,1763,277],{"class":90},[80,1765,823],{"class":107},[80,1767,277],{"class":90},[80,1769,224],{"class":90},[80,1771,104],{"class":90},[80,1773,832],{"class":107},[80,1775,277],{"class":90},[80,1777,294],{"class":274},[80,1779,1780],{"class":82,"line":264},[80,1781,160],{"emptyLinePlaceholder":159},[80,1783,1784,1787,1789,1792,1794,1797],{"class":82,"line":297},[80,1785,1786],{"class":94},"    connection",[80,1788,252],{"class":90},[80,1790,1791],{"class":212},"add",[80,1793,215],{"class":274},[80,1795,1796],{"class":94},"res",[80,1798,294],{"class":274},[80,1800,1801],{"class":82,"line":302},[80,1802,160],{"emptyLinePlaceholder":159},[80,1804,1805,1807,1809,1811,1813,1815,1818,1820,1822,1824,1826],{"class":82,"line":339},[80,1806,1178],{"class":94},[80,1808,252],{"class":90},[80,1810,1183],{"class":212},[80,1812,215],{"class":274},[80,1814,277],{"class":90},[80,1816,1817],{"class":107},"close",[80,1819,277],{"class":90},[80,1821,224],{"class":90},[80,1823,436],{"class":90},[80,1825,233],{"class":166},[80,1827,236],{"class":90},[80,1829,1830,1833,1835,1838,1840,1842],{"class":82,"line":367},[80,1831,1832],{"class":94},"      connection",[80,1834,252],{"class":90},[80,1836,1837],{"class":212},"delete",[80,1839,215],{"class":274},[80,1841,1796],{"class":94},[80,1843,294],{"class":274},[80,1845,1846,1848],{"class":82,"line":372},[80,1847,1221],{"class":90},[80,1849,294],{"class":274},[80,1851,1852],{"class":82,"line":389},[80,1853,160],{"emptyLinePlaceholder":159},[80,1855,1856],{"class":82,"line":402},[80,1857,1124],{"class":242},[80,1859,1860],{"class":82,"line":410},[80,1861,850],{"class":90},[80,1863,1864,1866,1868,1870,1872,1874,1876,1878,1880,1882,1884,1886],{"class":82,"line":415},[80,1865,855],{"class":86},[80,1867,1135],{"class":86},[80,1869,758],{"class":274},[80,1871,221],{"class":94},[80,1873,252],{"class":90},[80,1875,765],{"class":94},[80,1877,768],{"class":90},[80,1879,104],{"class":90},[80,1881,1150],{"class":107},[80,1883,277],{"class":90},[80,1885,777],{"class":274},[80,1887,780],{"class":90},[80,1889,1890,1892,1894,1896],{"class":82,"line":443},[80,1891,1161],{"class":166},[80,1893,1164],{"class":94},[80,1895,258],{"class":90},[80,1897,1169],{"class":90},[80,1899,1900],{"class":82,"line":487},[80,1901,160],{"emptyLinePlaceholder":159},[80,1903,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922,1924,1926,1928],{"class":82,"line":1000},[80,1905,1178],{"class":94},[80,1907,252],{"class":90},[80,1909,1183],{"class":212},[80,1911,215],{"class":274},[80,1913,277],{"class":90},[80,1915,1190],{"class":107},[80,1917,277],{"class":90},[80,1919,224],{"class":90},[80,1921,758],{"class":90},[80,1923,1199],{"class":220},[80,1925,230],{"class":90},[80,1927,233],{"class":166},[80,1929,236],{"class":90},[80,1931,1932,1934,1936],{"class":82,"line":1005},[80,1933,1210],{"class":94},[80,1935,1213],{"class":90},[80,1937,1216],{"class":94},[80,1939,1940,1942],{"class":82,"line":1325},[80,1941,1221],{"class":90},[80,1943,294],{"class":274},[80,1945,1946],{"class":82,"line":1330},[80,1947,160],{"emptyLinePlaceholder":159},[80,1949,1950,1952,1954,1956,1958,1960,1962,1964,1966,1968,1970],{"class":82,"line":1337},[80,1951,1178],{"class":94},[80,1953,252],{"class":90},[80,1955,1183],{"class":212},[80,1957,215],{"class":274},[80,1959,277],{"class":90},[80,1961,396],{"class":107},[80,1963,277],{"class":90},[80,1965,224],{"class":90},[80,1967,436],{"class":90},[80,1969,233],{"class":166},[80,1971,236],{"class":90},[80,1973,1974],{"class":82,"line":1342},[80,1975,1976],{"class":242},"      \u002F\u002F Broadcast the message to all connected clients\n",[80,1978,1979,1982,1984,1986,1989,1992,1995],{"class":82,"line":1347},[80,1980,1981],{"class":86},"      for",[80,1983,758],{"class":274},[80,1985,167],{"class":166},[80,1987,1988],{"class":94}," client",[80,1990,1991],{"class":90}," of",[80,1993,1994],{"class":94}," connection",[80,1996,294],{"class":274},[80,1998,1999,2002,2004,2006,2008,2010,2013,2015,2017,2019,2022,2024],{"class":82,"line":1352},[80,2000,2001],{"class":94},"        client",[80,2003,252],{"class":90},[80,2005,379],{"class":212},[80,2007,215],{"class":274},[80,2009,456],{"class":90},[80,2011,2012],{"class":107},"data: ",[80,2014,462],{"class":90},[80,2016,670],{"class":94},[80,2018,405],{"class":90},[80,2020,2021],{"class":94},"\\n\\n",[80,2023,456],{"class":90},[80,2025,294],{"class":274},[80,2027,2029],{"class":82,"line":2028},31,[80,2030,160],{"emptyLinePlaceholder":159},[80,2032,2034],{"class":82,"line":2033},32,[80,2035,1265],{"class":242},[80,2037,2039,2041,2043,2045,2047],{"class":82,"line":2038},33,[80,2040,1270],{"class":94},[80,2042,252],{"class":90},[80,2044,255],{"class":94},[80,2046,258],{"class":90},[80,2048,1279],{"class":193},[80,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068,2070,2072,2074],{"class":82,"line":2051},34,[80,2053,1270],{"class":94},[80,2055,252],{"class":90},[80,2057,271],{"class":212},[80,2059,215],{"class":274},[80,2061,277],{"class":90},[80,2063,1294],{"class":107},[80,2065,277],{"class":90},[80,2067,224],{"class":90},[80,2069,104],{"class":90},[80,2071,480],{"class":107},[80,2073,277],{"class":90},[80,2075,294],{"class":274},[80,2077,2079,2081,2083,2085],{"class":82,"line":2078},35,[80,2080,1270],{"class":94},[80,2082,252],{"class":90},[80,2084,396],{"class":212},[80,2086,399],{"class":274},[80,2088,2090,2092],{"class":82,"line":2089},36,[80,2091,1221],{"class":90},[80,2093,294],{"class":274},[80,2095,2097],{"class":82,"line":2096},37,[80,2098,850],{"class":90},[80,2100,2102,2104],{"class":82,"line":2101},38,[80,2103,855],{"class":86},[80,2105,236],{"class":90},[80,2107,2109],{"class":82,"line":2108},39,[80,2110,862],{"class":242},[80,2112,2114],{"class":82,"line":2113},40,[80,2115,1115],{"class":242},[80,2117,2119],{"class":82,"line":2118},41,[80,2120,850],{"class":90},[80,2122,2124,2126],{"class":82,"line":2123},42,[80,2125,405],{"class":90},[80,2127,294],{"class":94},[80,2129,2131],{"class":82,"line":2130},43,[80,2132,160],{"emptyLinePlaceholder":159},[80,2134,2136],{"class":82,"line":2135},44,[80,2137,715],{"class":242},[10,2139,2140,2141,2143,2144,2147],{},"When a client connects to the ",[46,2142,704],{}," endpoint, the server adds the response object to the ",[46,2145,2146],{},"connection"," set. When the connection is closed, the response object is removed from the set. Easy!",[10,2149,2150,2151,2153],{},"We keep this object to be able to send data to the client using the method ",[46,2152,379],{},". It's the heart of the system.",[10,2155,2156,2157,2159,2160,2162],{},"When a message is received on the ",[46,2158,1150],{}," endpoint, the server loops over the ",[46,2161,2146],{}," previously saved to send the message to every client.",[10,2164,2165,2166,2168,2169,2172,2173,2175,2176,2178,2179,2181,2182,252],{},"We can observe the ",[46,2167,1190],{}," keyword in the message and the double ",[46,2170,2171],{},"\\n"," at the end. This is the format of the Server-Sent Events. The ",[46,2174,1190],{}," keyword is used to send data to the client, and the double ",[46,2177,2171],{}," is used to tell the client that the message is finished. The client will only expose the content after the ",[46,2180,1190],{}," keyword and before the double ",[46,2183,2171],{},[10,2185,2186,2187,2190,2191,252],{},"For example, if the server sends ",[46,2188,2189],{},"data: Hello\\n\\n",", the client will see ",[46,2192,2193],{},"Hello",[31,2195,2197],{"id":2196},"eventsource-api","EventSource API",[10,2199,2200,2201,252],{},"Our server handles the connection and broadcasts messages, but the client is still not able to connect to the SSE endpoint, resource ",[46,2202,704],{},[10,2204,2205,2206,2209],{},"In order to connect to the server, we will use the ",[46,2207,2208],{},"EventSource"," API. This API is a native JavaScript API to handle Server-Sent Events, and it's very straightforward.",[10,2211,2212],{},"In the beginning of our file, we need to have a little script to connect and to handle messages:",[71,2214,2216],{"className":531,"code":2215,"language":384,"meta":76,"style":76},"\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n    \u003Cscript>\n      \u002F\u002F Connect to the server\n      const eventSource = new EventSource(\"\u002Fevents\");\n\n      const ul = document.querySelector(\"ul\");\n\n      \u002F\u002F Listen for message\n      eventSource.onmessage = (event) => {\n        \u002F\u002F Add the message to the list\n        const li = document.createElement(\"li\");\n        li.innerText = event.data;\n\n        ul.appendChild(li);\n      };\n    \u003C\u002Fscript>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cul>\u003C\u002Ful>\n\n    \u003Cform action=\"\u002Fmessage\" method=\"post\">\n      \u003Cinput type=\"text\" name=\"message\" \u002F>\n      \u003Cbutton type=\"submit\">Send\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[46,2217,2218,2228,2246,2254,2272,2300,2316,2325,2330,2358,2362,2391,2395,2400,2423,2428,2457,2478,2482,2500,2505,2513,2521,2529,2542,2546,2574,2602,2628,2636,2644],{"__ignoreMap":76},[80,2219,2220,2222,2224,2226],{"class":82,"line":83},[80,2221,539],{"class":90},[80,2223,542],{"class":274},[80,2225,344],{"class":166},[80,2227,547],{"class":90},[80,2229,2230,2232,2234,2236,2238,2240,2242,2244],{"class":82,"line":114},[80,2231,552],{"class":90},[80,2233,384],{"class":274},[80,2235,557],{"class":166},[80,2237,173],{"class":90},[80,2239,562],{"class":90},[80,2241,565],{"class":107},[80,2243,562],{"class":90},[80,2245,547],{"class":90},[80,2247,2248,2250,2252],{"class":82,"line":135},[80,2249,574],{"class":90},[80,2251,577],{"class":274},[80,2253,547],{"class":90},[80,2255,2256,2258,2260,2262,2264,2266,2268,2270],{"class":82,"line":156},[80,2257,584],{"class":90},[80,2259,587],{"class":274},[80,2261,590],{"class":166},[80,2263,173],{"class":90},[80,2265,562],{"class":90},[80,2267,597],{"class":107},[80,2269,562],{"class":90},[80,2271,602],{"class":90},[80,2273,2274,2276,2278,2280,2282,2284,2286,2288,2290,2292,2294,2296,2298],{"class":82,"line":163},[80,2275,584],{"class":90},[80,2277,587],{"class":274},[80,2279,611],{"class":166},[80,2281,173],{"class":90},[80,2283,562],{"class":90},[80,2285,618],{"class":107},[80,2287,562],{"class":90},[80,2289,623],{"class":166},[80,2291,173],{"class":90},[80,2293,562],{"class":90},[80,2295,630],{"class":107},[80,2297,562],{"class":90},[80,2299,602],{"class":90},[80,2301,2302,2304,2306,2308,2310,2312,2314],{"class":82,"line":183},[80,2303,584],{"class":90},[80,2305,641],{"class":274},[80,2307,644],{"class":90},[80,2309,647],{"class":94},[80,2311,650],{"class":90},[80,2313,641],{"class":274},[80,2315,547],{"class":90},[80,2317,2318,2320,2323],{"class":82,"line":197},[80,2319,584],{"class":90},[80,2321,2322],{"class":274},"script",[80,2324,547],{"class":90},[80,2326,2327],{"class":82,"line":202},[80,2328,2329],{"class":242},"      \u002F\u002F Connect to the server\n",[80,2331,2332,2335,2338,2340,2342,2345,2347,2349,2351,2353,2355],{"class":82,"line":239},[80,2333,2334],{"class":166},"      const",[80,2336,2337],{"class":94}," eventSource ",[80,2339,173],{"class":90},[80,2341,1662],{"class":90},[80,2343,2344],{"class":212}," EventSource",[80,2346,215],{"class":94},[80,2348,562],{"class":90},[80,2350,704],{"class":107},[80,2352,562],{"class":90},[80,2354,230],{"class":94},[80,2356,2357],{"class":90},";\n",[80,2359,2360],{"class":82,"line":246},[80,2361,160],{"emptyLinePlaceholder":159},[80,2363,2364,2366,2369,2371,2374,2376,2379,2381,2383,2385,2387,2389],{"class":82,"line":264},[80,2365,2334],{"class":166},[80,2367,2368],{"class":94}," ul ",[80,2370,173],{"class":90},[80,2372,2373],{"class":94}," document",[80,2375,252],{"class":90},[80,2377,2378],{"class":212},"querySelector",[80,2380,215],{"class":94},[80,2382,562],{"class":90},[80,2384,51],{"class":107},[80,2386,562],{"class":90},[80,2388,230],{"class":94},[80,2390,2357],{"class":90},[80,2392,2393],{"class":82,"line":297},[80,2394,160],{"emptyLinePlaceholder":159},[80,2396,2397],{"class":82,"line":302},[80,2398,2399],{"class":242},"      \u002F\u002F Listen for message\n",[80,2401,2402,2405,2407,2410,2412,2414,2417,2419,2421],{"class":82,"line":339},[80,2403,2404],{"class":94},"      eventSource",[80,2406,252],{"class":90},[80,2408,2409],{"class":212},"onmessage",[80,2411,258],{"class":90},[80,2413,758],{"class":90},[80,2415,2416],{"class":220},"event",[80,2418,230],{"class":90},[80,2420,233],{"class":166},[80,2422,236],{"class":90},[80,2424,2425],{"class":82,"line":367},[80,2426,2427],{"class":242},"        \u002F\u002F Add the message to the list\n",[80,2429,2430,2433,2436,2438,2440,2442,2445,2447,2449,2451,2453,2455],{"class":82,"line":372},[80,2431,2432],{"class":166},"        const",[80,2434,2435],{"class":94}," li",[80,2437,258],{"class":90},[80,2439,2373],{"class":94},[80,2441,252],{"class":90},[80,2443,2444],{"class":212},"createElement",[80,2446,215],{"class":274},[80,2448,562],{"class":90},[80,2450,54],{"class":107},[80,2452,562],{"class":90},[80,2454,230],{"class":274},[80,2456,2357],{"class":90},[80,2458,2459,2462,2464,2467,2469,2472,2474,2476],{"class":82,"line":389},[80,2460,2461],{"class":94},"        li",[80,2463,252],{"class":90},[80,2465,2466],{"class":94},"innerText",[80,2468,258],{"class":90},[80,2470,2471],{"class":94}," event",[80,2473,252],{"class":90},[80,2475,1190],{"class":94},[80,2477,2357],{"class":90},[80,2479,2480],{"class":82,"line":402},[80,2481,160],{"emptyLinePlaceholder":159},[80,2483,2484,2487,2489,2492,2494,2496,2498],{"class":82,"line":410},[80,2485,2486],{"class":94},"        ul",[80,2488,252],{"class":90},[80,2490,2491],{"class":212},"appendChild",[80,2493,215],{"class":274},[80,2495,54],{"class":94},[80,2497,230],{"class":274},[80,2499,2357],{"class":90},[80,2501,2502],{"class":82,"line":415},[80,2503,2504],{"class":90},"      };\n",[80,2506,2507,2509,2511],{"class":82,"line":443},[80,2508,1589],{"class":90},[80,2510,2322],{"class":274},[80,2512,547],{"class":90},[80,2514,2515,2517,2519],{"class":82,"line":487},[80,2516,659],{"class":90},[80,2518,577],{"class":274},[80,2520,547],{"class":90},[80,2522,2523,2525,2527],{"class":82,"line":1000},[80,2524,574],{"class":90},[80,2526,670],{"class":274},[80,2528,547],{"class":90},[80,2530,2531,2533,2535,2538,2540],{"class":82,"line":1005},[80,2532,584],{"class":90},[80,2534,51],{"class":274},[80,2536,2537],{"class":90},">\u003C\u002F",[80,2539,51],{"class":274},[80,2541,547],{"class":90},[80,2543,2544],{"class":82,"line":1325},[80,2545,160],{"emptyLinePlaceholder":159},[80,2547,2548,2550,2552,2554,2556,2558,2560,2562,2564,2566,2568,2570,2572],{"class":82,"line":1330},[80,2549,584],{"class":90},[80,2551,1497],{"class":274},[80,2553,1500],{"class":166},[80,2555,173],{"class":90},[80,2557,562],{"class":90},[80,2559,1150],{"class":107},[80,2561,562],{"class":90},[80,2563,1511],{"class":166},[80,2565,173],{"class":90},[80,2567,562],{"class":90},[80,2569,1518],{"class":107},[80,2571,562],{"class":90},[80,2573,547],{"class":90},[80,2575,2576,2578,2580,2582,2584,2586,2588,2590,2592,2594,2596,2598,2600],{"class":82,"line":1337},[80,2577,1527],{"class":90},[80,2579,1530],{"class":274},[80,2581,1533],{"class":166},[80,2583,173],{"class":90},[80,2585,562],{"class":90},[80,2587,1540],{"class":107},[80,2589,562],{"class":90},[80,2591,611],{"class":166},[80,2593,173],{"class":90},[80,2595,562],{"class":90},[80,2597,1551],{"class":107},[80,2599,562],{"class":90},[80,2601,602],{"class":90},[80,2603,2604,2606,2608,2610,2612,2614,2616,2618,2620,2622,2624,2626],{"class":82,"line":1342},[80,2605,1527],{"class":90},[80,2607,1562],{"class":274},[80,2609,1533],{"class":166},[80,2611,173],{"class":90},[80,2613,562],{"class":90},[80,2615,1571],{"class":107},[80,2617,562],{"class":90},[80,2619,644],{"class":90},[80,2621,1578],{"class":94},[80,2623,650],{"class":90},[80,2625,1562],{"class":274},[80,2627,547],{"class":90},[80,2629,2630,2632,2634],{"class":82,"line":1347},[80,2631,1589],{"class":90},[80,2633,1497],{"class":274},[80,2635,547],{"class":90},[80,2637,2638,2640,2642],{"class":82,"line":1352},[80,2639,659],{"class":90},[80,2641,670],{"class":274},[80,2643,547],{"class":90},[80,2645,2646,2648,2650],{"class":82,"line":2028},[80,2647,650],{"class":90},[80,2649,384],{"class":274},[80,2651,547],{"class":90},[10,2653,2654,2655,2657,2658,2660],{},"The script starts by asking for the resource ",[46,2656,704],{}," from the server using the ",[46,2659,2208],{}," API. This will automatically handle the reconnection.",[10,2662,2663,2664,2666],{},"Then, we register a method ",[46,2665,2409],{}," that will be triggered every time a message is received by the client from the server.",[10,2668,2669,2670,2672],{},"To have something more like a chat, we will add a list to display the messages. When a message is received, we will create a ",[46,2671,54],{}," element and append it to the list.",[10,2674,2675],{},"Now, we can open two tabs in our browser and send messages. It will appear in the other tab. :magic:",[10,2677,2678],{},[1623,2679],{"alt":2680,"src":2681},"Node SSE Chat","\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fnode-sse-chat.gif",[31,2683,2685],{"id":2684},"final-words","Final words",[10,2687,2688],{},"Even if this is a very simple example, remember that the logic of storing clients and broadcasting with a loop is the same as a more complex application.",[10,2690,2691],{},"There are two things to remember with Server-Sent Events:",[51,2693,2694,2700],{},[54,2695,2696,2697,2699],{},"Use the ",[46,2698,2208],{}," API to connect to the server",[54,2701,2702,2703,2705],{},"Respond with ",[46,2704,1016],{}," from the server",[10,2707,2708],{},"Nothing more. It's simple, it's HTTP, and it's powerful.",[10,2710,2711],{},"I hope this article will help you to understand how Server-Sent Events work and to give you the confidence to use it in your projects. It's a very powerful tool to build real-time applications without the complexity of WebSockets.",[2713,2714,2715],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":76,"searchDepth":114,"depth":114,"links":2717},[2718,2719,2720,2721,2722,2723,2724,2725],{"id":33,"depth":114,"text":34},{"id":40,"depth":114,"text":41},{"id":522,"depth":114,"text":523},{"id":694,"depth":114,"text":695},{"id":1035,"depth":114,"text":1036},{"id":1612,"depth":114,"text":1613},{"id":2196,"depth":114,"text":2197},{"id":2684,"depth":114,"text":2685},"c2fe30ca-712c-40d6-93d3-7f4dead62a2f","2024-04-03","Create real-time Node.js apps using Server-Sent Events for effective server-client communication without the complexity of WebSockets.","md",{},"\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events","---\ncontentId: c2fe30ca-712c-40d6-93d3-7f4dead62a2f\ntitle: Create Real-Time Node.js Apps with Server-Sent Events\ndescription: Create real-time Node.js apps using Server-Sent Events for effective server-client communication without the complexity of WebSockets.\ndate: 2024-04-03\n---\n\nI'm deeply convinced that to understand a concept or a technology, we all need to try **to cook something with it** using the lowest level possible. This is not only related to programming but to everything in life.\n\nIn the continuation of the article [Real-time without WebSocket](\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets), I think that building a little client-server application using Server-Sent Events (SSE) is a good way to understand how it works and to give us the confidence to use it in our projects.\n\nThis client-server application will be a little chat where the client will send a message to the server and the server will broadcast the message to all connected clients. No external library, no framework, just Node.js.\n\n## Requirements\n\nTo follow this tutorial, we need to have Node.js installed on our machine. That's all.\n\n## The server\n\nCreate a file named `server.mjs`. This file will contain the server code to perform the following actions:\n\n- Serve the client-side code aka a simple HTML file\n- Receive messages from clients\n- Accept Server-Sent Events connections and broadcast messages\n\nBut for now, let's begin by serving the client-side code. Our server will be built using the native Node.js `http` module.\n\n```js\nimport { readFileSync } from 'node:fs'\nimport { createServer } from 'node:http'\nimport { join } from 'node:path'\n\nconst hostname = '127.0.0.1'\nconst port = 3000\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Serve the index.html file\n  res.statusCode = 200\n  res.setHeader('Content-Type', 'text\u002Fhtml')\n\n  const htmlFile = join(process.cwd(), 'index.html')\n  const html = readFileSync(htmlFile, 'utf-8')\n\n  res.write(html)\n  res.end()\n})\n\nserver.listen(port, hostname, () => {\n  console.log(`Server running at http:\u002F\u002F${hostname}:${port}\u002F`)\n})\n```\n\nWhen a request hits the server, it will respond with the content of the `index.html` file by reading it on the disk. The server is listening on the port `3000`.\n\nWe can start our server with the following command:\n\n```sh\nnode server.mjs\n```\n\n## The client\n\nCreate a file named `index.html` at the root of the project. This file will contain the client-side code:\n\n```html\n\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\nYeah, that's a very simple HTML file, nothing fancy for now.\n\n## Server-sent events connection\n\nNow, let's add the Server-Sent Events connection to the server. To handle SSE, we need to respond with some headers to keep the connection open and to send data. So the client will ask for a resource and the server will respond with a stream.\n\nThis resource will be `\u002Fevents`. Let's update the server code:\n\n```js\n\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n  \u002F\u002F `\u002Fevents` endpoint used to listen for messages\n  if (req.url === '\u002Fevents') {\n    res.setHeader('Content-Type', 'text\u002Fevent-stream')\n    res.setHeader('Cache-Control', 'no-cache')\n\n    \u002F\u002F TODO: Handle the connection\n  }\n  else {\n    \u002F\u002F Serve the index.html file\n    res.statusCode = 200\n    res.setHeader('Content-Type', 'text\u002Fhtml')\n\n    const htmlFile = join(process.cwd(), 'index.html')\n    const html = readFileSync(htmlFile, 'utf-8')\n\n    res.write(html)\n    res.end()\n  }\n})\n\n\u002F\u002F ...\n```\n\nWhen the client asks for the resource `\u002Fevents`, the server will respond with the headers `Content-Type: text\u002Fevent-stream`, and `Cache-Control: no-cache`. This will tell the client that the connection is a stream, i.e. the server will send data to the client at any time. The connection must not be cached to avoid issues with the stream.\nIt's important to note that we never call the function `res.end()`. This is because **the connection must stay open** to allow the server to send data to the client. The function `res.end()` closes the connection.\n\n## Chat logic\n\nThis is very simple and simplified to the maximum, but the logic behind remains the same as a more complex application.\n\nIn our server, we will have a logic to receive messages from clients.\n\n```js\n\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n\u002F\u002F `\u002Fevents` endpoint used to listen for messages\n  if (req.url === '\u002Fevents') {\n    \u002F\u002F ...\n\n    \u002F\u002F `\u002Fmessages` endpoint used to post messages\n  }\n  else if (req.url === '\u002Fmessage') {\n    let body = ''\n\n    req.on('data', (chunk) => {\n      body += chunk\n    })\n\n    req.on('end', () => {\n      \u002F\u002F TODO: broadcast the message\n\n      \u002F\u002F Redirect to the index.html file\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Serve the index.html file\n    \u002F\u002F ...\n  }\n})\n```\n\nWhen the URL requested is `\u002Fmessage`, the server will listen for the data sent by the client, which is the body of the request. When the request is finished, the server redirects the client to the `index.html` file. This is a classic behavior when a form is submitted for a server-rendered application.\n\nIn our `index.html` file, add a form to send messages to the server:\n\n```html\n\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cform action=\"\u002Fmessage\" method=\"post\">\n      \u003Cinput type=\"text\" name=\"message\" \u002F>\n      \u003Cbutton type=\"submit\">Send\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\n## Broadcast the message\n\nThe missing feature is the broadcast of the message. How do we broadcast the message and what does it mean?\n\nA broadcast is when a message is sent to everyone. Otherwise, it's a unicast, when a message is sent to a specific person.\n\n![Broadcast vs Unicast](\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fbroadcast-vs-unicast.png)\n\nIn our case, the client A will send a message to the server (unicast). Then, the server will send this message to every client (broadcast).\n\nThe unicast has been implemented in the previous section. For the broadcast, we need to keep track of the connected clients to loop over them when a message is received.\n\n```js\n\u002F\u002F ...\n\n\u002F\u002F Keep track of the connected clients\nconst connection = new Set()\n\nconst server = createServer((req, res) => {\n  \u002F\u002F `\u002Fevents` endpoint used to listen for messages\n  if (req.url === '\u002Fevents') {\n    res.setHeader('Content-Type', 'text\u002Fevent-stream')\n    res.setHeader('Cache-Control', 'no-cache')\n\n    connection.add(res)\n\n    req.on('close', () => {\n      connection.delete(res)\n    })\n\n    \u002F\u002F `\u002Fmessages` endpoint used to post messages\n  }\n  else if (req.url === '\u002Fmessage') {\n    let body = ''\n\n    req.on('data', (chunk) => {\n      body += chunk\n    })\n\n    req.on('end', () => {\n      \u002F\u002F Broadcast the message to all connected clients\n      for (const client of connection)\n        client.write(`data: ${body}\\n\\n`)\n\n      \u002F\u002F Redirect to the index.html file\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Serve the index.html file\n    \u002F\u002F ...\n  }\n})\n\n\u002F\u002F ...\n```\n\nWhen a client connects to the `\u002Fevents` endpoint, the server adds the response object to the `connection` set. When the connection is closed, the response object is removed from the set. Easy!\n\nWe keep this object to be able to send data to the client using the method `write`. It's the heart of the system.\n\nWhen a message is received on the `\u002Fmessage` endpoint, the server loops over the `connection` previously saved to send the message to every client.\n\nWe can observe the `data` keyword in the message and the double `\\n` at the end. This is the format of the Server-Sent Events. The `data` keyword is used to send data to the client, and the double `\\n` is used to tell the client that the message is finished. The client will only expose the content after the `data` keyword and before the double `\\n`.\n\nFor example, if the server sends `data: Hello\\n\\n`, the client will see `Hello`.\n\n## EventSource API\n\nOur server handles the connection and broadcasts messages, but the client is still not able to connect to the SSE endpoint, resource `\u002Fevents`.\n\nIn order to connect to the server, we will use the `EventSource` API. This API is a native JavaScript API to handle Server-Sent Events, and it's very straightforward.\n\nIn the beginning of our file, we need to have a little script to connect and to handle messages:\n\n```html\n\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n    \u003Cscript>\n      \u002F\u002F Connect to the server\n      const eventSource = new EventSource(\"\u002Fevents\");\n\n      const ul = document.querySelector(\"ul\");\n\n      \u002F\u002F Listen for message\n      eventSource.onmessage = (event) => {\n        \u002F\u002F Add the message to the list\n        const li = document.createElement(\"li\");\n        li.innerText = event.data;\n\n        ul.appendChild(li);\n      };\n    \u003C\u002Fscript>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cul>\u003C\u002Ful>\n\n    \u003Cform action=\"\u002Fmessage\" method=\"post\">\n      \u003Cinput type=\"text\" name=\"message\" \u002F>\n      \u003Cbutton type=\"submit\">Send\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\nThe script starts by asking for the resource `\u002Fevents` from the server using the `EventSource` API. This will automatically handle the reconnection.\n\nThen, we register a method `onmessage` that will be triggered every time a message is received by the client from the server.\n\nTo have something more like a chat, we will add a list to display the messages. When a message is received, we will create a `li` element and append it to the list.\n\nNow, we can open two tabs in our browser and send messages. It will appear in the other tab. :magic:\n\n![Node SSE Chat](\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fnode-sse-chat.gif)\n\n## Final words\n\nEven if this is a very simple example, remember that the logic of storing clients and broadcasting with a loop is the same as a more complex application.\n\nThere are two things to remember with Server-Sent Events:\n\n- Use the `EventSource` API to connect to the server\n- Respond with `Content-Type: text\u002Fevent-stream` from the server\n\nNothing more. It's simple, it's HTTP, and it's powerful.\n\nI hope this article will help you to understand how Server-Sent Events work and to give you the confidence to use it in your projects. It's a very powerful tool to build real-time applications without the complexity of WebSockets.\n",null,{"title":5,"description":2728},{"loc":2731,"images":2736},[2737,2738],{"loc":1626},{"loc":2681},"posts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events","eYqDrkiPCAnwO49gIgBRIWIGR8rtpHsaGrGtOLNP-fc",1790086901993]