[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"navigation":3,"blog-page":58,"blogs":76},[4],{"title":5,"path":6,"stem":7,"children":8,"page":57},"Blog","\u002Fblog","blog",[9,13,17,21,25,29,33,37,41,45,49,53],{"title":10,"path":11,"stem":12},"A Guide to Server-Side Rendering (SSR) with Vite and React.js","\u002Fblog\u002Fa-guide-to-server-side-rendering-ssr-with-vite-and-reactjs","blog\u002Fa-guide-to-server-side-rendering-ssr-with-vite-and-reactjs",{"title":14,"path":15,"stem":16},"🔄 Background DNS Refresh in Node.js: Keeping Cached IPs Updated","\u002Fblog\u002Fbackground-dns-refresh-in-nodejs-keeping-cached-ips-updated","blog\u002Fbackground-dns-refresh-in-nodejs-keeping-cached-ips-updated",{"title":18,"path":19,"stem":20},"Comprehensive Guide: Encrypting Images, Uploading to Cloudinary, and Serving Them Securely","\u002Fblog\u002Fcomprehensive-guide-encrypting-images-uploading-to-cloudinary-and-serving-them-securely","blog\u002Fcomprehensive-guide-encrypting-images-uploading-to-cloudinary-and-serving-them-securely",{"title":22,"path":23,"stem":24},"Comprehensive Guide to Using Uploadthing with Express and Typescript","\u002Fblog\u002Fcomprehensive-guide-to-using-uploadthing-with-express-and-typescript","blog\u002Fcomprehensive-guide-to-using-uploadthing-with-express-and-typescript",{"title":26,"path":27,"stem":28},"🌐 DNS Caching in Node.js: Supercharge Your App's Speed & Reliability 🚀","\u002Fblog\u002Fdns-caching-in-nodejs-supercharge-your-apps-speed-and-reliability","blog\u002Fdns-caching-in-nodejs-supercharge-your-apps-speed-and-reliability",{"title":30,"path":31,"stem":32},"Enhancing Redis for Message Queues: Using External Databases for Payload Storage","\u002Fblog\u002Fenhancing-redis-for-message-queues-using-external-databases-for-payload-storage","blog\u002Fenhancing-redis-for-message-queues-using-external-databases-for-payload-storage",{"title":34,"path":35,"stem":36},"Full Stack Learning Management System (LMS) with MERN Stack: A Complete Guide","\u002Fblog\u002Ffull-stack-learning-management-system-lms-with-mern-stack-a-complete-guide","blog\u002Ffull-stack-learning-management-system-lms-with-mern-stack-a-complete-guide",{"title":38,"path":39,"stem":40},"Getting Started with Redis: Elevating Your Web Applications","\u002Fblog\u002Fgetting-started-with-redis-elevating-your-web-applications","blog\u002Fgetting-started-with-redis-elevating-your-web-applications",{"title":42,"path":43,"stem":44},"How Queues Enhance Your Application Development Process","\u002Fblog\u002Fhow-queues-enhance-your-application-development-process","blog\u002Fhow-queues-enhance-your-application-development-process",{"title":46,"path":47,"stem":48},"Next-Gen E-Learning: Transforming Education for Students and Creators","\u002Fblog\u002Fnext-gen-e-learning-transforming-education-for-students-and-creators","blog\u002Fnext-gen-e-learning-transforming-education-for-students-and-creators",{"title":50,"path":51,"stem":52},"Single Sign-On (SSO) Implementation Using Passport.js in TypeScript","\u002Fblog\u002Fsingle-sign-on-sso-implementation-using-passportjs-in-typescript","blog\u002Fsingle-sign-on-sso-implementation-using-passportjs-in-typescript",{"title":54,"path":55,"stem":56},"Using Multer for Local File Uploads: Benefits, Trade-offs, and Cloud Transfer Integration","\u002Fblog\u002Fusing-multer-for-local-file-uploads-benefits-trade-offs-and-cloud-transfer-integration","blog\u002Fusing-multer-for-local-file-uploads-benefits-trade-offs-and-cloud-transfer-integration",false,{"id":59,"title":60,"body":61,"description":62,"extension":63,"links":64,"meta":72,"navigation":73,"path":6,"seo":74,"stem":7,"__hash__":75},"pages\u002Fblog.yml","Latest Articles",null,"Articles on backend development, Node.js, TypeScript, and web technologies — things I'm building and learning along the way.","yml",[65],{"label":66,"to":67,"icon":68,"color":69,"variant":70,"target":71},"Visit my Hashnode blog","https:\u002F\u002Fsachinthapa.hashnode.dev\u002F","i-simple-icons-hashnode","primary","solid","_blank",{},true,{"title":60,"description":62},"jSvyRoGlSq6gwP0dU7cMIEyKy-GXfu2jcmx_SpmUQ9U",[77,1074,2565,2951,3693,4006,4362,4694,4946,5155,5715,5969],{"id":78,"title":22,"author":79,"body":84,"date":1067,"description":1068,"extension":1069,"image":1070,"meta":1071,"minRead":233,"navigation":73,"path":23,"seo":1072,"stem":24,"__hash__":1073},"blog\u002Fblog\u002Fcomprehensive-guide-to-using-uploadthing-with-express-and-typescript.md",{"name":80,"to":67,"username":81,"avatar":82},"Sachin Thapa","sachinthapa572",{"src":83,"alt":80},"https:\u002F\u002Fcdn.hashnode.com\u002Fres\u002Fhashnode\u002Fimage\u002Fupload\u002Fv1733546636355\u002Fa4ae8ce5-eb4c-4ed8-ab9d-15a4b8f32ea5.png",{"type":85,"value":86,"toc":1064},"minimark",[87,91,96,99,103,106,779,783,786,813,817,826,834,837,845,849,852,954,999,1003,1006,1010,1013,1016,1021,1060],[88,89,90],"p",{},"This note provides a detailed exploration of setting up file uploads using Uploadthing in an Express.js application with Typescript, focusing on security best practices and the potential use of signed URLs. The information is derived from analyzing Uploadthing's documentation, ensuring a thorough understanding for developers seeking to implement this service.",[92,93,95],"h4",{"id":94},"overview-of-uploadthing","Overview of Uploadthing",[88,97,98],{},"Uploadthing is a file upload solution designed for modern web developers, particularly those using full-stack TypeScript applications. It supports various frameworks like Next.js, Astro, Solid Start, SvelteKit, and Nuxt, with backend adapters including Express, Fastify, H3, and WinterCG\u002FFetch API, and frontend libraries such as React, Vue, SolidJS, Svelte, and Vanilla JS. This versatility makes it suitable for diverse project needs, especially in Express environments with Typescript.",[92,100,102],{"id":101},"step-by-step-setup-process","Step-by-Step Setup Process",[88,104,105],{},"To integrate Uploadthing with Express and Typescript, follow these detailed steps:",[107,108,109,127,141,532,761],"ol",{},[110,111,112,116],"li",{},[113,114,115],"strong",{},"Package Installation:",[117,118,119],"ul",{},[110,120,121,122,126],{},"Begin by installing the Uploadthing package using the command ",[123,124,125],"code",{},"npm install uploadthing",". This installs the necessary dependencies for both server and client-side operations.",[110,128,129,132],{},[113,130,131],{},"Environment Configuration:",[117,133,134],{},[110,135,136,137,140],{},"Access the Uploadthing dashboard to create a new application and retrieve an API key from the API Keys tab. Set this key as an environment variable in your project, typically as ",[123,138,139],{},"UPLOADTHING_TOKEN=your_token",". This ensures secure interaction with the Uploadthing SDK.",[110,142,143,146],{},[113,144,145],{},"File Router Creation:",[117,147,148,523],{},[110,149,150,151,154,155,158,159],{},"Create a file, such as ",[123,152,153],{},"src\u002Fuploadthing.ts",", to define your file routes using the ",[123,156,157],{},"createUploadthing"," function from \"uploadthing\u002Fexpress\". For example, to set up an image uploader:\n",[160,161,166],"pre",{"className":162,"code":163,"language":164,"meta":165,"style":165},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { createUploadthing } from \"uploadthing\u002Fexpress\";\n\nconst f = createUploadthing();\n\nconst imageUploader = f({ \"image\": { maxFileSize: \"4MB\", maxFileCount: 1, acl: \"private\" } })\n  .middleware(async ({ req }) => {\n    \u002F\u002F Security check: Ensure user is authenticated\n    if (!req.user) {\n      throw new Error(\"Unauthorized\");\n    }\n  })\n  .onUploadComplete(({ file }) => {\n    \u002F\u002F Post-upload actions: Log or store the file URL\n    console.log(file.url);\n  });\n\nexport const uploadRouter = f({\n  imageUploader,\n});\n","ts","",[123,167,168,204,210,231,236,313,343,350,377,403,409,417,439,445,470,479,484,504,513],{"__ignoreMap":165},[169,170,173,177,181,185,188,191,194,198,201],"span",{"class":171,"line":172},"line",1,[169,174,176],{"class":175},"s7zQu","import",[169,178,180],{"class":179},"sMK4o"," {",[169,182,184],{"class":183},"sTEyZ"," createUploadthing",[169,186,187],{"class":179}," }",[169,189,190],{"class":175}," from",[169,192,193],{"class":179}," \"",[169,195,197],{"class":196},"sfazB","uploadthing\u002Fexpress",[169,199,200],{"class":179},"\"",[169,202,203],{"class":179},";\n",[169,205,207],{"class":171,"line":206},2,[169,208,209],{"emptyLinePlaceholder":73},"\n",[169,211,213,217,220,223,226,229],{"class":171,"line":212},3,[169,214,216],{"class":215},"spNyl","const",[169,218,219],{"class":183}," f ",[169,221,222],{"class":179},"=",[169,224,184],{"class":225},"s2Zo4",[169,227,228],{"class":183},"()",[169,230,203],{"class":179},[169,232,234],{"class":171,"line":233},4,[169,235,209],{"emptyLinePlaceholder":73},[169,237,239,241,244,246,249,252,255,257,261,263,266,268,271,273,275,278,280,283,286,288,292,294,297,299,301,304,306,308,310],{"class":171,"line":238},5,[169,240,216],{"class":215},[169,242,243],{"class":183}," imageUploader ",[169,245,222],{"class":179},[169,247,248],{"class":225}," f",[169,250,251],{"class":183},"(",[169,253,254],{"class":179},"{",[169,256,193],{"class":179},[169,258,260],{"class":259},"swJcz","image",[169,262,200],{"class":179},[169,264,265],{"class":179},":",[169,267,180],{"class":179},[169,269,270],{"class":259}," maxFileSize",[169,272,265],{"class":179},[169,274,193],{"class":179},[169,276,277],{"class":196},"4MB",[169,279,200],{"class":179},[169,281,282],{"class":179},",",[169,284,285],{"class":259}," maxFileCount",[169,287,265],{"class":179},[169,289,291],{"class":290},"sbssI"," 1",[169,293,282],{"class":179},[169,295,296],{"class":259}," acl",[169,298,265],{"class":179},[169,300,193],{"class":179},[169,302,303],{"class":196},"private",[169,305,200],{"class":179},[169,307,187],{"class":179},[169,309,187],{"class":179},[169,311,312],{"class":183},")\n",[169,314,316,319,322,324,327,330,334,337,340],{"class":171,"line":315},6,[169,317,318],{"class":179},"  .",[169,320,321],{"class":225},"middleware",[169,323,251],{"class":183},[169,325,326],{"class":215},"async",[169,328,329],{"class":179}," ({",[169,331,333],{"class":332},"sHdIc"," req",[169,335,336],{"class":179}," })",[169,338,339],{"class":215}," =>",[169,341,342],{"class":179}," {\n",[169,344,346],{"class":171,"line":345},7,[169,347,349],{"class":348},"sHwdD","    \u002F\u002F Security check: Ensure user is authenticated\n",[169,351,353,356,359,362,365,368,371,374],{"class":171,"line":352},8,[169,354,355],{"class":175},"    if",[169,357,358],{"class":259}," (",[169,360,361],{"class":179},"!",[169,363,364],{"class":183},"req",[169,366,367],{"class":179},".",[169,369,370],{"class":183},"user",[169,372,373],{"class":259},") ",[169,375,376],{"class":179},"{\n",[169,378,380,383,386,389,391,393,396,398,401],{"class":171,"line":379},9,[169,381,382],{"class":175},"      throw",[169,384,385],{"class":179}," new",[169,387,388],{"class":225}," Error",[169,390,251],{"class":259},[169,392,200],{"class":179},[169,394,395],{"class":196},"Unauthorized",[169,397,200],{"class":179},[169,399,400],{"class":259},")",[169,402,203],{"class":179},[169,404,406],{"class":171,"line":405},10,[169,407,408],{"class":179},"    }\n",[169,410,412,415],{"class":171,"line":411},11,[169,413,414],{"class":179},"  }",[169,416,312],{"class":183},[169,418,420,422,425,427,430,433,435,437],{"class":171,"line":419},12,[169,421,318],{"class":179},[169,423,424],{"class":225},"onUploadComplete",[169,426,251],{"class":183},[169,428,429],{"class":179},"({",[169,431,432],{"class":332}," file",[169,434,336],{"class":179},[169,436,339],{"class":215},[169,438,342],{"class":179},[169,440,442],{"class":171,"line":441},13,[169,443,444],{"class":348},"    \u002F\u002F Post-upload actions: Log or store the file URL\n",[169,446,448,451,453,456,458,461,463,466,468],{"class":171,"line":447},14,[169,449,450],{"class":183},"    console",[169,452,367],{"class":179},[169,454,455],{"class":225},"log",[169,457,251],{"class":259},[169,459,460],{"class":183},"file",[169,462,367],{"class":179},[169,464,465],{"class":183},"url",[169,467,400],{"class":259},[169,469,203],{"class":179},[169,471,473,475,477],{"class":171,"line":472},15,[169,474,414],{"class":179},[169,476,400],{"class":183},[169,478,203],{"class":179},[169,480,482],{"class":171,"line":481},16,[169,483,209],{"emptyLinePlaceholder":73},[169,485,487,490,493,496,498,500,502],{"class":171,"line":486},17,[169,488,489],{"class":175},"export",[169,491,492],{"class":215}," const",[169,494,495],{"class":183}," uploadRouter ",[169,497,222],{"class":179},[169,499,248],{"class":225},[169,501,251],{"class":183},[169,503,376],{"class":179},[169,505,507,510],{"class":171,"line":506},18,[169,508,509],{"class":183},"  imageUploader",[169,511,512],{"class":179},",\n",[169,514,516,519,521],{"class":171,"line":515},19,[169,517,518],{"class":179},"}",[169,520,400],{"class":183},[169,522,203],{"class":179},[110,524,525,526,528,529,531],{},"The ",[123,527,321],{}," function allows for authorization checks, such as verifying user login status, while ",[123,530,424],{}," handles post-upload tasks, providing access to file properties like the URL.",[110,533,534,537],{},[113,535,536],{},"Server Integration:",[117,538,539,758],{},[110,540,541,542,545,546,549,550,553,554],{},"In your main server file, typically ",[123,543,544],{},"src\u002Findex.ts",", integrate the file router with Express using the ",[123,547,548],{},"createRouteHandler"," from \"uploadthing\u002Fexpress\". Mount it at a specific endpoint, such as ",[123,551,552],{},"\u002Fapi\u002Fuploadthing",":\n",[160,555,557],{"className":162,"code":556,"language":164,"meta":165,"style":165},"import express from \"express\";\nimport { createRouteHandler } from \"uploadthing\u002Fexpress\";\nimport { uploadRouter } from \".\u002Fuploadthing\";\n\nconst app = express();\n\n\u002F\u002F Mount the Uploadthing route\napp.use(\"\u002Fapi\u002Fuploadthing\", createRouteHandler({\n  router: uploadRouter,\n}));\n\n\u002F\u002F Start the server\napp.listen(3000, () => {\n  console.log(\"Server started on port 3000\");\n});\n",[123,558,559,578,599,621,625,641,645,650,676,687,696,700,705,728,750],{"__ignoreMap":165},[169,560,561,563,566,569,571,574,576],{"class":171,"line":172},[169,562,176],{"class":175},[169,564,565],{"class":183}," express ",[169,567,568],{"class":175},"from",[169,570,193],{"class":179},[169,572,573],{"class":196},"express",[169,575,200],{"class":179},[169,577,203],{"class":179},[169,579,580,582,584,587,589,591,593,595,597],{"class":171,"line":206},[169,581,176],{"class":175},[169,583,180],{"class":179},[169,585,586],{"class":183}," createRouteHandler",[169,588,187],{"class":179},[169,590,190],{"class":175},[169,592,193],{"class":179},[169,594,197],{"class":196},[169,596,200],{"class":179},[169,598,203],{"class":179},[169,600,601,603,605,608,610,612,614,617,619],{"class":171,"line":212},[169,602,176],{"class":175},[169,604,180],{"class":179},[169,606,607],{"class":183}," uploadRouter",[169,609,187],{"class":179},[169,611,190],{"class":175},[169,613,193],{"class":179},[169,615,616],{"class":196},".\u002Fuploadthing",[169,618,200],{"class":179},[169,620,203],{"class":179},[169,622,623],{"class":171,"line":233},[169,624,209],{"emptyLinePlaceholder":73},[169,626,627,629,632,634,637,639],{"class":171,"line":238},[169,628,216],{"class":215},[169,630,631],{"class":183}," app ",[169,633,222],{"class":179},[169,635,636],{"class":225}," express",[169,638,228],{"class":183},[169,640,203],{"class":179},[169,642,643],{"class":171,"line":315},[169,644,209],{"emptyLinePlaceholder":73},[169,646,647],{"class":171,"line":345},[169,648,649],{"class":348},"\u002F\u002F Mount the Uploadthing route\n",[169,651,652,655,657,660,662,664,666,668,670,672,674],{"class":171,"line":352},[169,653,654],{"class":183},"app",[169,656,367],{"class":179},[169,658,659],{"class":225},"use",[169,661,251],{"class":183},[169,663,200],{"class":179},[169,665,552],{"class":196},[169,667,200],{"class":179},[169,669,282],{"class":179},[169,671,586],{"class":225},[169,673,251],{"class":183},[169,675,376],{"class":179},[169,677,678,681,683,685],{"class":171,"line":379},[169,679,680],{"class":259},"  router",[169,682,265],{"class":179},[169,684,607],{"class":183},[169,686,512],{"class":179},[169,688,689,691,694],{"class":171,"line":405},[169,690,518],{"class":179},[169,692,693],{"class":183},"))",[169,695,203],{"class":179},[169,697,698],{"class":171,"line":411},[169,699,209],{"emptyLinePlaceholder":73},[169,701,702],{"class":171,"line":419},[169,703,704],{"class":348},"\u002F\u002F Start the server\n",[169,706,707,709,711,714,716,719,721,724,726],{"class":171,"line":441},[169,708,654],{"class":183},[169,710,367],{"class":179},[169,712,713],{"class":225},"listen",[169,715,251],{"class":183},[169,717,718],{"class":290},"3000",[169,720,282],{"class":179},[169,722,723],{"class":179}," ()",[169,725,339],{"class":215},[169,727,342],{"class":179},[169,729,730,733,735,737,739,741,744,746,748],{"class":171,"line":447},[169,731,732],{"class":183},"  console",[169,734,367],{"class":179},[169,736,455],{"class":225},[169,738,251],{"class":259},[169,740,200],{"class":179},[169,742,743],{"class":196},"Server started on port 3000",[169,745,200],{"class":179},[169,747,400],{"class":259},[169,749,203],{"class":179},[169,751,752,754,756],{"class":171,"line":472},[169,753,518],{"class":179},[169,755,400],{"class":183},[169,757,203],{"class":179},[110,759,760],{},"This setup ensures that file upload requests are handled through the defined endpoint.",[110,762,763,766],{},[113,764,765],{},"Client-Side Implementation:",[117,767,768],{},[110,769,770,771,774,775,778],{},"On the client side, utilize the ",[123,772,773],{},"@uploadthing\u002Freact"," package to generate upload components, such as an upload button. Configure it with the server's endpoint, for example, ",[123,776,777],{},"https:\u002F\u002Fyour-server.com\u002Fapi\u002Fuploadthing",". This allows users to initiate uploads seamlessly within your application.",[92,780,782],{"id":781},"security-best-practices","Security Best Practices",[88,784,785],{},"Security is paramount when handling file uploads. Uploadthing provides several mechanisms to ensure secure operations:",[117,787,788,803],{},[110,789,790,793,794],{},[113,791,792],{},"Middleware for Authentication:"," Implement middleware within the file router to authorize users before uploads. For instance, check if a user is logged in using a service like Clerk, throwing an error if unauthorized:\n",[117,795,796],{},[110,797,798,799,802],{},"Example: ",[123,800,801],{},"throw new Error(\"You must be logged in to upload a profile picture\")"," if no user is signed in.",[110,804,805,808,809,812],{},[113,806,807],{},"Access Control Lists (ACL):"," Since version 6.0, Uploadthing allows setting the ",[123,810,811],{},"acl"," property in route configurations to \"public-read\" or \"private\". Setting it to \"private\" restricts access, ensuring only authorized users can view or download files. This is crucial for protecting sensitive data.",[92,814,816],{"id":815},"handling-file-access-and-signed-urls","Handling File Access and Signed URLs",[88,818,819,820,822,823,825],{},"Accessing uploaded files is facilitated through the ",[123,821,424],{}," callback, which provides a ",[123,824,460],{}," object containing properties like the URL. For example:",[117,827,828],{},[110,829,525,830,833],{},[123,831,832],{},"file.url"," can be used to access the uploaded file, which is particularly useful for storing in a database or sharing within the application.",[88,835,836],{},"However, for files with \"private\" ACL, direct access via URL may require additional authorization. While Uploadthing's documentation does not explicitly detail signed URLs, the concept aligns with cloud storage practices like Amazon S3, where signed URLs provide temporary access to private files. Given Uploadthing's mention of being a \"better S3\" alternative, it seems likely that similar mechanisms exist, but specific implementation details are not provided. Developers may need to:",[117,838,839,842],{},[110,840,841],{},"Refer to the storage provider's documentation (if Uploadthing uses an underlying service like S3) or",[110,843,844],{},"Contact Uploadthing support for guidance on generating signed URLs for sharing private files.",[92,846,848],{"id":847},"tables-for-clarity","Tables for Clarity",[88,850,851],{},"To organize the information, consider the following tables summarizing key aspects:",[853,854,855,877],"table",{},[856,857,858],"thead",{},[859,860,861,867,872],"tr",{},[862,863,864],"th",{},[113,865,866],{},"Step",[862,868,869],{},[113,870,871],{},"Action",[862,873,874],{},[113,875,876],{},"Details",[878,879,880,894,907,923,940],"tbody",{},[859,881,882,886,891],{},[883,884,885],"td",{},"1. Install Package",[883,887,888,889],{},"Run ",[123,890,125],{},[883,892,893],{},"Installs server and client dependencies",[859,895,896,899,904],{},[883,897,898],{},"2. Environment Setup",[883,900,901,902],{},"Set ",[123,903,139],{},[883,905,906],{},"Secure SDK interaction with API key",[859,908,909,912,920],{},[883,910,911],{},"3. File Router Creation",[883,913,914,915,917,918],{},"Define routes in ",[123,916,153],{}," using ",[123,919,157],{},[883,921,922],{},"Example: Image uploader with 4MB limit, private ACL",[859,924,925,928,934],{},[883,926,927],{},"4. Server Integration",[883,929,930,931,933],{},"Mount at ",[123,932,552],{}," in Express server",[883,935,936,937,939],{},"Use ",[123,938,548],{}," for routing",[859,941,942,945,950],{},[883,943,944],{},"5. Client Usage",[883,946,936,947,949],{},[123,948,773],{}," for upload button, set endpoint URL",[883,951,798,952],{},[123,953,777],{},[853,955,956,975],{},[856,957,958],{},[859,959,960,965,970],{},[862,961,962],{},[113,963,964],{},"Security Aspect",[862,966,967],{},[113,968,969],{},"Description",[862,971,972],{},[113,973,974],{},"Implementation",[878,976,977,988],{},[859,978,979,982,985],{},[883,980,981],{},"Middleware Authentication",[883,983,984],{},"Ensure only authorized users can upload files",[883,986,987],{},"Check user login status, throw error if unauthorized",[859,989,990,993,996],{},[883,991,992],{},"Access Control (ACL)",[883,994,995],{},"Set file access to \"public-read\" or \"private\"",[883,997,998],{},"Configure in route, affects file visibility",[92,1000,1002],{"id":1001},"unexpected-findings","Unexpected Findings",[88,1004,1005],{},"An interesting observation is Uploadthing's positioning as an alternative to S3, suggesting it uses similar cloud storage technologies. This implies potential familiarity with concepts like signed URLs, but the lack of explicit documentation on this topic adds complexity, requiring developers to seek additional resources or support.",[92,1007,1009],{"id":1008},"conclusion","Conclusion",[88,1011,1012],{},"This guide provides a comprehensive approach to setting up Uploadthing with Express and Typescript, emphasizing security through authentication and access controls. While accessing uploaded files is straightforward via the provided URL, handling private files with signed URLs may require further exploration, potentially through Uploadthing's support channels or underlying storage provider documentation.",[1014,1015],"hr",{},[1017,1018,1020],"h3",{"id":1019},"key-citations","Key Citations",[117,1022,1023,1032,1039,1046,1053],{},[110,1024,1025],{},[1026,1027,1031],"a",{"href":1028,"rel":1029},"https:\u002F\u002Fdocs.uploadthing.com\u002F",[1030],"nofollow","Uploadthing Getting Started Guide",[110,1033,1034],{},[1026,1035,1038],{"href":1036,"rel":1037},"https:\u002F\u002Fdocs.uploadthing.com\u002Fbackend-adapters\u002Fexpress",[1030],"Uploadthing Express Adapter Documentation",[110,1040,1041],{},[1026,1042,1045],{"href":1043,"rel":1044},"https:\u002F\u002Fdocs.uploadthing.com\u002Ffile-routes",[1030],"Uploadthing File Routes Documentation",[110,1047,1048],{},[1026,1049,1052],{"href":1050,"rel":1051},"https:\u002F\u002Fdocs.uploadthing.com\u002Fapi-reference\u002Fserver#create-routehandler",[1030],"Uploadthing Server API Reference",[110,1054,1055],{},[1026,1056,1059],{"href":1057,"rel":1058},"https:\u002F\u002Fdocs.uploadthing.com\u002Fapi-reference\u002Fserver#ut-api",[1030],"Uploadthing UTApi Reference",[1061,1062,1063],"style",{},"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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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);}",{"title":165,"searchDepth":206,"depth":206,"links":1065},[1066],{"id":1019,"depth":212,"text":1020},"2026-07-18","This note provides a detailed exploration of setting up file uploads using Uploadthing in an Express.js application with TypeScript, focusing on security best practices and the potential use of signed URLs.","md","\u002Fimages\u002Fblog\u002Fcomprehensive-guide-to-using-uploadthing-with-express-and-typescript.png",{},{"title":22,"description":1068},"HEzjWFhxyCCWG8xQQ9kaMNpQQfoRZAJZCufyrr9Oudo",{"id":1075,"title":34,"author":1076,"body":1078,"date":2559,"description":2560,"extension":1069,"image":2561,"meta":2562,"minRead":379,"navigation":73,"path":35,"seo":2563,"stem":36,"__hash__":2564},"blog\u002Fblog\u002Ffull-stack-learning-management-system-lms-with-mern-stack-a-complete-guide.md",{"name":80,"to":67,"username":81,"avatar":1077},{"src":83,"alt":80},{"type":85,"value":1079,"toc":2533},[1080,1087,1089,1095,1133,1135,1141,1147,1206,1208,1214,1363,1365,1371,1474,1476,1482,1488,1495,1503,1510,1516,1523,1529,1535,1549,1555,1614,1620,1664,1670,1678,1684,1702,1704,1710,1754,1756,1760,1763,1766,1769,1775,1836,1842,1914,1920,1962,1968,2027,2033,2065,2071,2103,2109,2151,2157,2189,2195,2237,2243,2275,2281,2303,2309,2331,2337,2358,2364,2386,2392,2414,2420,2452,2454,2460,2522,2524,2530],[88,1081,1082,1083,1086],{},"This document provides a structured approach to developing a Learning Management System (LMS) using the ",[113,1084,1085],{},"MERN stack"," (MongoDB, Express.js, React.js, and Node.js). An LMS typically includes features like course management, student enrollment, assessments, and content delivery.",[1014,1088],{},[1017,1090,1092],{"id":1091},"project-overview",[113,1093,1094],{},"Project Overview",[117,1096,1097,1103,1109,1115,1121,1127],{},[110,1098,1099,1102],{},[113,1100,1101],{},"Frontend",": React.js for building the user interface (UI).",[110,1104,1105,1108],{},[113,1106,1107],{},"Backend",": Node.js and Express.js for the server and API layer.",[110,1110,1111,1114],{},[113,1112,1113],{},"Database",": MongoDB for storing user data, courses, assignments, etc.",[110,1116,1117,1120],{},[113,1118,1119],{},"Authentication",": JWT (JSON Web Tokens) for secure user authentication.",[110,1122,1123,1126],{},[113,1124,1125],{},"File Storage",": Use a cloud storage service like AWS S3 or an alternative for storing course material (videos, PDFs).",[110,1128,1129,1132],{},[113,1130,1131],{},"Hosting",": Use platforms like Heroku, Vercel (frontend), and MongoDB Atlas for database hosting.",[1014,1134],{},[1017,1136,1138],{"id":1137},"project-structure",[113,1139,1140],{},"Project Structure",[92,1142,1144],{"id":1143},"_1-frontend-reactjs",[113,1145,1146],{},"1. Frontend (React.js)",[117,1148,1149,1186,1192],{},[110,1150,1151,265,1154],{},[113,1152,1153],{},"Components",[117,1155,1156,1162,1168,1174,1180],{},[110,1157,1158,1161],{},[113,1159,1160],{},"Navbar",": Links to the dashboard, courses, login, and sign-up.",[110,1163,1164,1167],{},[113,1165,1166],{},"Dashboard",": Display an overview of enrolled courses, progress, etc.",[110,1169,1170,1173],{},[113,1171,1172],{},"Course List",": Displays all available courses.",[110,1175,1176,1179],{},[113,1177,1178],{},"Course Details",": Detailed view of course content, assignments, and quizzes.",[110,1181,1182,1185],{},[113,1183,1184],{},"Profile",": User profile and progress.",[110,1187,1188,1191],{},[113,1189,1190],{},"State Management",": Use Redux or React Context API to manage the global state (such as user login, course data, etc.).",[110,1193,1194,1197,1198,1201,1202,1205],{},[113,1195,1196],{},"API Integration",": Use ",[123,1199,1200],{},"axios"," or ",[123,1203,1204],{},"fetch"," to make HTTP requests to your backend APIs.",[1014,1207],{},[92,1209,1211],{"id":1210},"_2-backend-nodejs-expressjs",[113,1212,1213],{},"2. Backend (Node.js & Express.js)",[117,1215,1216,1259],{},[110,1217,1218,1221,1222],{},[113,1219,1220],{},"API Structure",": Organize your backend using the MVC (Model-View-Controller) pattern.",[117,1223,1224,1230,1247],{},[110,1225,1226,1229],{},[113,1227,1228],{},"Controllers",": Handle logic for each feature (e.g., courses, users).",[110,1231,1232,1235,1236,1239,1240,1239,1243,1246],{},[113,1233,1234],{},"Models",": Define schemas for MongoDB (e.g., ",[123,1237,1238],{},"User",", ",[123,1241,1242],{},"Course",[123,1244,1245],{},"Assignment",").",[110,1248,1249,1252,1253,1239,1256,1246],{},[113,1250,1251],{},"Routes",": Create routes for different resources (e.g., ",[123,1254,1255],{},"\u002Fapi\u002Fcourses",[123,1257,1258],{},"\u002Fapi\u002Fusers",[110,1260,1261,265,1264],{},[113,1262,1263],{},"API Endpoints",[117,1265,1266,1270,1276,1282,1287,1293,1299,1305,1311,1317,1322,1328,1334,1340,1346,1351,1357],{},[110,1267,1268,265],{},[113,1269,1119],{},[110,1271,1272,1275],{},[123,1273,1274],{},"POST \u002Fapi\u002Fauth\u002Fregister",": Register new users (admin, teacher, student).",[110,1277,1278,1281],{},[123,1279,1280],{},"POST \u002Fapi\u002Fauth\u002Flogin",": Login and return a JWT token.",[110,1283,1284,265],{},[113,1285,1286],{},"Course Management",[110,1288,1289,1292],{},[123,1290,1291],{},"POST \u002Fapi\u002Fcourses",": Admin or teacher can create courses.",[110,1294,1295,1298],{},[123,1296,1297],{},"GET \u002Fapi\u002Fcourses",": Fetch all available courses for students.",[110,1300,1301,1304],{},[123,1302,1303],{},"GET \u002Fapi\u002Fcourses\u002F:id",": Fetch detailed course data.",[110,1306,1307,1310],{},[123,1308,1309],{},"PUT \u002Fapi\u002Fcourses\u002F:id",": Update a course.",[110,1312,1313,1316],{},[123,1314,1315],{},"DELETE \u002Fapi\u002Fcourses\u002F:id",": Delete a course.",[110,1318,1319,265],{},[113,1320,1321],{},"Assignments & Quizzes",[110,1323,1324,1327],{},[123,1325,1326],{},"POST \u002Fapi\u002Fcourses\u002F:id\u002Fassignments",": Add assignments for a course.",[110,1329,1330,1333],{},[123,1331,1332],{},"GET \u002Fapi\u002Fcourses\u002F:id\u002Fassignments",": Fetch assignments.",[110,1335,1336,1339],{},[123,1337,1338],{},"POST \u002Fapi\u002Fcourses\u002F:id\u002Fquizzes",": Add quizzes for a course.",[110,1341,1342,1345],{},[123,1343,1344],{},"GET \u002Fapi\u002Fcourses\u002F:id\u002Fquizzes",": Fetch quizzes.",[110,1347,1348,265],{},[113,1349,1350],{},"Progress Tracking",[110,1352,1353,1356],{},[123,1354,1355],{},"GET \u002Fapi\u002Fusers\u002F:id\u002Fprogress",": Fetch user progress in courses.",[110,1358,1359,1362],{},[123,1360,1361],{},"PUT \u002Fapi\u002Fusers\u002F:id\u002Fprogress",": Update user progress.",[1014,1364],{},[1017,1366,1368],{"id":1367},"key-features",[113,1369,1370],{},"Key Features",[107,1372,1373,1398,1413,1426,1439,1452,1462],{},[110,1374,1375,265,1378],{},[113,1376,1377],{},"User Roles",[117,1379,1380,1386,1392],{},[110,1381,1382,1385],{},[113,1383,1384],{},"Admin",": Manage users, courses, assignments, and quizzes.",[110,1387,1388,1391],{},[113,1389,1390],{},"Teacher",": Create and manage courses, upload assignments and quizzes.",[110,1393,1394,1397],{},[113,1395,1396],{},"Student",": Enroll in courses, complete assignments, take quizzes.",[110,1399,1400,265,1402],{},[113,1401,1286],{},[117,1403,1404,1407,1410],{},[110,1405,1406],{},"CRUD operations for courses (Create, Read, Update, Delete).",[110,1408,1409],{},"Upload course material (PDF, video, etc.).",[110,1411,1412],{},"Quizzes and assignments as part of each course.",[110,1414,1415,265,1418],{},[113,1416,1417],{},"Student Enrollment",[117,1419,1420,1423],{},[110,1421,1422],{},"Students can browse courses and enroll in them.",[110,1424,1425],{},"Each student can track their progress and grades for enrolled courses.",[110,1427,1428,265,1431],{},[113,1429,1430],{},"Authentication & Authorization",[117,1432,1433,1436],{},[110,1434,1435],{},"JWT-based authentication for login and registration.",[110,1437,1438],{},"Role-based access control (RBAC) to ensure only authorized users (admin\u002Fteacher\u002Fstudent) can perform specific actions.",[110,1440,1441,265,1444],{},[113,1442,1443],{},"Assignment Submission",[117,1445,1446,1449],{},[110,1447,1448],{},"Students can upload their assignment submissions.",[110,1450,1451],{},"Teachers can grade assignments and provide feedback.",[110,1453,1454,265,1457],{},[113,1455,1456],{},"Quizzes",[117,1458,1459],{},[110,1460,1461],{},"Quizzes as part of the course content with auto-grading or manual grading by the teacher.",[110,1463,1464,265,1466],{},[113,1465,1350],{},[117,1467,1468,1471],{},[110,1469,1470],{},"Track student progress for each course.",[110,1472,1473],{},"Display progress on the dashboard (e.g., completion percentage).",[1014,1475],{},[1017,1477,1479],{"id":1478},"steps-to-develop-the-lms",[113,1480,1481],{},"Steps to Develop the LMS",[92,1483,1485],{"id":1484},"step-1-set-up-the-project",[113,1486,1487],{},"Step 1: Set Up the Project",[107,1489,1490],{},[110,1491,1492,265],{},[113,1493,1494],{},"Initialize Git Repository",[160,1496,1501],{"className":1497,"code":1499,"language":1500},[1498],"language-text","   git init\n","text",[123,1502,1499],{"__ignoreMap":165},[107,1504,1505],{},[110,1506,1507,265],{},[113,1508,1509],{},"Frontend Setup (React)",[160,1511,1514],{"className":1512,"code":1513,"language":1500},[1498],"   npx create-react-app lms-frontend\n   cd lms-frontend\n",[123,1515,1513],{"__ignoreMap":165},[107,1517,1518],{},[110,1519,1520,265],{},[113,1521,1522],{},"Backend Setup (Node.js & Express)",[160,1524,1527],{"className":1525,"code":1526,"language":1500},[1498],"   mkdir lms-backend\n   cd lms-backend\n   npm init -y\n   npm install express mongoose bcryptjs jsonwebtoken cors\n",[123,1528,1526],{"__ignoreMap":165},[92,1530,1532],{"id":1531},"step-2-database-setup-mongodb",[113,1533,1534],{},"Step 2: Database Setup (MongoDB)",[117,1536,1537,1543],{},[110,1538,1539,1542],{},[113,1540,1541],{},"MongoDB Atlas",": Create a MongoDB Atlas account and set up a cloud database.",[110,1544,1545,1548],{},[113,1546,1547],{},"Local MongoDB",": If preferred, install MongoDB locally.",[92,1550,1552],{"id":1551},"step-3-backend-development",[113,1553,1554],{},"Step 3: Backend Development",[107,1556,1557,1578,1594,1604],{},[110,1558,1559,265,1562],{},[113,1560,1561],{},"Define Models (MongoDB)",[117,1563,1564],{},[110,1565,1566,1567,1239,1569,1239,1571,1573,1574,1577],{},"Create models for ",[113,1568,1238],{},[113,1570,1242],{},[113,1572,1245],{},", and ",[113,1575,1576],{},"Quiz"," using Mongoose.",[110,1579,1580,265,1583],{},[113,1581,1582],{},"User Authentication",[117,1584,1585,1591],{},[110,1586,936,1587,1590],{},[123,1588,1589],{},"bcryptjs"," to hash passwords.",[110,1592,1593],{},"Implement JWT-based authentication for login and registration.",[110,1595,1596,265,1599],{},[113,1597,1598],{},"API Routes",[117,1600,1601],{},[110,1602,1603],{},"Set up API routes for user management, course CRUD operations, assignments, and quizzes.",[110,1605,1606,265,1609],{},[113,1607,1608],{},"Middleware",[117,1610,1611],{},[110,1612,1613],{},"Create authentication middleware to protect routes (e.g., only authenticated users can access certain routes).",[92,1615,1617],{"id":1616},"step-4-frontend-development",[113,1618,1619],{},"Step 4: Frontend Development",[107,1621,1622,1632,1641,1654],{},[110,1623,1624,265,1627],{},[113,1625,1626],{},"React Components",[117,1628,1629],{},[110,1630,1631],{},"Build UI components for course management, enrollment, dashboard, and profile.",[110,1633,1634,265,1636],{},[113,1635,1190],{},[117,1637,1638],{},[110,1639,1640],{},"Use Redux or React Context API to handle global state (e.g., user data, enrolled courses, etc.).",[110,1642,1643,265,1645],{},[113,1644,1196],{},[117,1646,1647],{},[110,1648,936,1649,1201,1651,1653],{},[123,1650,1200],{},[123,1652,1204],{}," to connect frontend to backend APIs.",[110,1655,1656,265,1659],{},[113,1657,1658],{},"Protected Routes",[117,1660,1661],{},[110,1662,1663],{},"Use React Router for navigation and protect routes using authentication checks (e.g., only logged-in users can access certain pages).",[92,1665,1667],{"id":1666},"step-5-file-storage-aws-s3-or-alternatives",[113,1668,1669],{},"Step 5: File Storage (AWS S3 or Alternatives)",[117,1671,1672,1675],{},[110,1673,1674],{},"Use AWS S3 or a similar service to store and retrieve files (course material, assignments).",[110,1676,1677],{},"Integrate the file upload functionality in the backend and create upload endpoints.",[92,1679,1681],{"id":1680},"step-6-testing",[113,1682,1683],{},"Step 6: Testing",[117,1685,1686,1691,1696],{},[110,1687,1688,1690],{},[113,1689,1101],{},": Test components using React Testing Library.",[110,1692,1693,1695],{},[113,1694,1107],{},": Use tools like Postman or Insomnia to test API endpoints.",[110,1697,1698,1701],{},[113,1699,1700],{},"End-to-End Testing",": Use Cypress for testing the entire application flow.",[1014,1703],{},[1017,1705,1707],{"id":1706},"deployment",[113,1708,1709],{},"Deployment",[107,1711,1712,1727,1743],{},[110,1713,1714,265,1716],{},[113,1715,1101],{},[117,1717,1718],{},[110,1719,1720,1721,1201,1724,367],{},"Deploy the React app on ",[113,1722,1723],{},"Vercel",[113,1725,1726],{},"Netlify",[110,1728,1729,265,1731],{},[113,1730,1107],{},[117,1732,1733],{},[110,1734,1735,1736,1239,1739,1742],{},"Deploy the backend on ",[113,1737,1738],{},"Heroku",[113,1740,1741],{},"DigitalOcean",", or any Node.js hosting service.",[110,1744,1745,265,1747],{},[113,1746,1113],{},[117,1748,1749],{},[110,1750,936,1751,1753],{},[113,1752,1541],{}," for cloud-hosted MongoDB.",[1014,1755],{},[1017,1757,1758],{"id":1008},[113,1759,1009],{},[88,1761,1762],{},"By following this guide, you can develop a full-fledged Learning Management System using the MERN stack. The system can be further expanded by adding features like real-time chat, discussion forums, notifications, and more.",[88,1764,1765],{},"If you need help with specific sections like setting up authentication, deploying the app, or adding advanced features, feel free to ask!",[88,1767,1768],{},"In a full-stack Learning Management System (LMS) developed with the MERN stack, a wide range of features and functionalities can be implemented. Here’s a breakdown of key features and their corresponding functionalities that you can consider for your project:",[1017,1770,1772],{"id":1771},"_1-user-management",[113,1773,1774],{},"1. User Management",[117,1776,1777,1786,1796,1806,1816,1826],{},[110,1778,1779,265,1781],{},[113,1780,1377],{},[117,1782,1783],{},[110,1784,1785],{},"Admin, Teacher, Student.",[110,1787,1788,265,1791],{},[113,1789,1790],{},"Registration",[117,1792,1793],{},[110,1794,1795],{},"Role-based user registration (Student, Teacher, Admin).",[110,1797,1798,265,1801],{},[113,1799,1800],{},"Login\u002FLogout",[117,1802,1803],{},[110,1804,1805],{},"JWT-based authentication for session management.",[110,1807,1808,265,1811],{},[113,1809,1810],{},"Profile Management",[117,1812,1813],{},[110,1814,1815],{},"Allow users to update their profile information (name, email, password).",[110,1817,1818,265,1821],{},[113,1819,1820],{},"Password Reset",[117,1822,1823],{},[110,1824,1825],{},"Password reset functionality using email or SMS.",[110,1827,1828,265,1831],{},[113,1829,1830],{},"Role-based Access Control (RBAC)",[117,1832,1833],{},[110,1834,1835],{},"Implement permissions based on user roles.",[1017,1837,1839],{"id":1838},"_2-course-management",[113,1840,1841],{},"2. Course Management",[117,1843,1844,1854,1864,1874,1884,1894,1904],{},[110,1845,1846,265,1849],{},[113,1847,1848],{},"Course Creation",[117,1850,1851],{},[110,1852,1853],{},"Teachers and Admins can create courses, including uploading videos, PDFs, or other course materials.",[110,1855,1856,265,1859],{},[113,1857,1858],{},"Course Editing",[117,1860,1861],{},[110,1862,1863],{},"Update or modify course content, titles, descriptions, and associated media.",[110,1865,1866,265,1869],{},[113,1867,1868],{},"Course Deletion",[117,1870,1871],{},[110,1872,1873],{},"Admins or course creators can delete courses.",[110,1875,1876,265,1879],{},[113,1877,1878],{},"Course Categories",[117,1880,1881],{},[110,1882,1883],{},"Add categories or tags to organize courses by subject.",[110,1885,1886,265,1889],{},[113,1887,1888],{},"Course Enrollment",[117,1890,1891],{},[110,1892,1893],{},"Students can browse and enroll in available courses.",[110,1895,1896,265,1899],{},[113,1897,1898],{},"Course Prerequisites",[117,1900,1901],{},[110,1902,1903],{},"Set prerequisites for enrolling in advanced courses.",[110,1905,1906,265,1909],{},[113,1907,1908],{},"Course Recommendations",[117,1910,1911],{},[110,1912,1913],{},"Suggest courses based on the student's interest or previously taken courses.",[1017,1915,1917],{"id":1916},"_3-content-delivery",[113,1918,1919],{},"3. Content Delivery",[117,1921,1922,1932,1942,1952],{},[110,1923,1924,265,1927],{},[113,1925,1926],{},"Video Lectures",[117,1928,1929],{},[110,1930,1931],{},"Upload and stream course videos from cloud storage (e.g., AWS S3).",[110,1933,1934,265,1937],{},[113,1935,1936],{},"PDF\u002FDocument Uploads",[117,1938,1939],{},[110,1940,1941],{},"Allow teachers to upload reading materials, slides, and other documents.",[110,1943,1944,265,1947],{},[113,1945,1946],{},"Content Sequencing",[117,1948,1949],{},[110,1950,1951],{},"Organize content into modules or units, and require students to complete them sequentially.",[110,1953,1954,265,1957],{},[113,1955,1956],{},"Course Progression",[117,1958,1959],{},[110,1960,1961],{},"Track and display course progression for students (e.g., 50% complete).",[1017,1963,1965],{"id":1964},"_4-assignments-quizzes",[113,1966,1967],{},"4. Assignments & Quizzes",[117,1969,1970,1980,1989,1998,2007,2017],{},[110,1971,1972,265,1975],{},[113,1973,1974],{},"Assignment Creation",[117,1976,1977],{},[110,1978,1979],{},"Teachers can create assignments with due dates and attach documents.",[110,1981,1982,265,1984],{},[113,1983,1443],{},[117,1985,1986],{},[110,1987,1988],{},"Students can upload assignment files or submit their work online.",[110,1990,1991,265,1994],{},[113,1992,1993],{},"Assignment Grading",[117,1995,1996],{},[110,1997,1451],{},[110,1999,2000,265,2002],{},[113,2001,1456],{},[117,2003,2004],{},[110,2005,2006],{},"Add multiple-choice or short-answer quizzes.",[110,2008,2009,265,2012],{},[113,2010,2011],{},"Auto-Grading for Quizzes",[117,2013,2014],{},[110,2015,2016],{},"Automatically grade multiple-choice quizzes.",[110,2018,2019,265,2022],{},[113,2020,2021],{},"Quiz Results",[117,2023,2024],{},[110,2025,2026],{},"Display quiz results and grades for students.",[1017,2028,2030],{"id":2029},"_5-student-progress-tracking",[113,2031,2032],{},"5. Student Progress Tracking",[117,2034,2035,2045,2055],{},[110,2036,2037,265,2040],{},[113,2038,2039],{},"Progress Dashboard",[117,2041,2042],{},[110,2043,2044],{},"Show students their progress in all enrolled courses.",[110,2046,2047,265,2050],{},[113,2048,2049],{},"Completion Certificates",[117,2051,2052],{},[110,2053,2054],{},"Award certificates for course completion.",[110,2056,2057,265,2060],{},[113,2058,2059],{},"Gradebook",[117,2061,2062],{},[110,2063,2064],{},"Teachers can maintain a gradebook that displays student performance across assignments and quizzes.",[1017,2066,2068],{"id":2067},"_6-discussion-forums",[113,2069,2070],{},"6. Discussion Forums",[117,2072,2073,2083,2093],{},[110,2074,2075,265,2078],{},[113,2076,2077],{},"Course-Specific Forums",[117,2079,2080],{},[110,2081,2082],{},"Allow students to discuss topics related to the course.",[110,2084,2085,265,2088],{},[113,2086,2087],{},"Threaded Discussions",[117,2089,2090],{},[110,2091,2092],{},"Support for threaded discussions with replies and comments.",[110,2094,2095,265,2098],{},[113,2096,2097],{},"Moderation",[117,2099,2100],{},[110,2101,2102],{},"Teachers and Admins can moderate discussions, remove inappropriate posts, and ban users if necessary.",[1017,2104,2106],{"id":2105},"_7-messaging-and-notifications",[113,2107,2108],{},"7. Messaging and Notifications",[117,2110,2111,2121,2131,2141],{},[110,2112,2113,265,2116],{},[113,2114,2115],{},"Messaging System",[117,2117,2118],{},[110,2119,2120],{},"Implement a direct messaging system between students, teachers, and admins.",[110,2122,2123,265,2126],{},[113,2124,2125],{},"Email Notifications",[117,2127,2128],{},[110,2129,2130],{},"Send email alerts for assignment deadlines, new course enrollment, and quiz results.",[110,2132,2133,265,2136],{},[113,2134,2135],{},"Push Notifications",[117,2137,2138],{},[110,2139,2140],{},"(Optional) Push notifications for real-time updates on mobile devices or browsers.",[110,2142,2143,265,2146],{},[113,2144,2145],{},"In-app Notifications",[117,2147,2148],{},[110,2149,2150],{},"Display in-app alerts for important updates.",[1017,2152,2154],{"id":2153},"_8-grading-and-evaluation",[113,2155,2156],{},"8. Grading and Evaluation",[117,2158,2159,2169,2179],{},[110,2160,2161,265,2164],{},[113,2162,2163],{},"Grading System",[117,2165,2166],{},[110,2167,2168],{},"Implement a grading system for assignments and quizzes.",[110,2170,2171,265,2174],{},[113,2172,2173],{},"Feedback Mechanism",[117,2175,2176],{},[110,2177,2178],{},"Allow teachers to provide detailed feedback on assignments and quizzes.",[110,2180,2181,265,2184],{},[113,2182,2183],{},"Final Grades",[117,2185,2186],{},[110,2187,2188],{},"Calculate final course grades based on assignments, quizzes, and other evaluation methods.",[1017,2190,2192],{"id":2191},"_9-reports-and-analytics",[113,2193,2194],{},"9. Reports and Analytics",[117,2196,2197,2207,2217,2227],{},[110,2198,2199,265,2202],{},[113,2200,2201],{},"Student Reports",[117,2203,2204],{},[110,2205,2206],{},"Generate reports on student performance and activity.",[110,2208,2209,265,2212],{},[113,2210,2211],{},"Teacher Reports",[117,2213,2214],{},[110,2215,2216],{},"Track how students are progressing in courses created by a teacher.",[110,2218,2219,265,2222],{},[113,2220,2221],{},"Admin Dashboard",[117,2223,2224],{},[110,2225,2226],{},"Display platform-wide statistics such as the number of users, active courses, enrollment statistics, etc.",[110,2228,2229,265,2232],{},[113,2230,2231],{},"Course Engagement Analytics",[117,2233,2234],{},[110,2235,2236],{},"Show how many students have completed or engaged with specific course content.",[1017,2238,2240],{"id":2239},"_10-payment-integration",[113,2241,2242],{},"10. Payment Integration",[117,2244,2245,2255,2265],{},[110,2246,2247,265,2250],{},[113,2248,2249],{},"Course Purchases",[117,2251,2252],{},[110,2253,2254],{},"Implement paid courses using a payment gateway like Stripe or PayPal.",[110,2256,2257,265,2260],{},[113,2258,2259],{},"Subscription Plans",[117,2261,2262],{},[110,2263,2264],{},"Offer subscription models where users pay for monthly or yearly access to all courses.",[110,2266,2267,265,2270],{},[113,2268,2269],{},"Refund System",[117,2271,2272],{},[110,2273,2274],{},"Allow users to request refunds for paid courses.",[1017,2276,2278],{"id":2277},"_11-real-time-features",[113,2279,2280],{},"11. Real-time Features",[117,2282,2283,2293],{},[110,2284,2285,265,2288],{},[113,2286,2287],{},"Real-time Classroom\u002FLive Sessions",[117,2289,2290],{},[110,2291,2292],{},"Integrate with video conferencing APIs (e.g., Zoom, Google Meet) to allow live classes.",[110,2294,2295,265,2298],{},[113,2296,2297],{},"Real-time Chat",[117,2299,2300],{},[110,2301,2302],{},"Add real-time chat for student-to-student or student-to-teacher interaction during courses or lectures.",[1017,2304,2306],{"id":2305},"_12-content-search-and-filtering",[113,2307,2308],{},"12. Content Search and Filtering",[117,2310,2311,2321],{},[110,2312,2313,265,2316],{},[113,2314,2315],{},"Search Functionality",[117,2317,2318],{},[110,2319,2320],{},"Implement a search bar to search for courses, teachers, or specific content.",[110,2322,2323,265,2326],{},[113,2324,2325],{},"Filters",[117,2327,2328],{},[110,2329,2330],{},"Allow users to filter courses by categories, difficulty level, price, and instructor.",[1017,2332,2334],{"id":2333},"_13-certificates-and-badges",[113,2335,2336],{},"13. Certificates and Badges",[117,2338,2339,2348],{},[110,2340,2341,265,2343],{},[113,2342,2049],{},[117,2344,2345],{},[110,2346,2347],{},"Automatically generate a certificate for students upon course completion.",[110,2349,2350,265,2353],{},[113,2351,2352],{},"Badging System",[117,2354,2355],{},[110,2356,2357],{},"Award badges for achievements like course completion, top quiz scorer, etc.",[1017,2359,2361],{"id":2360},"_14-mobile-responsiveness-and-compatibility",[113,2362,2363],{},"14. Mobile Responsiveness and Compatibility",[117,2365,2366,2376],{},[110,2367,2368,265,2371],{},[113,2369,2370],{},"Responsive Design",[117,2372,2373],{},[110,2374,2375],{},"Ensure the UI is fully responsive and works well on mobile devices and tablets.",[110,2377,2378,265,2381],{},[113,2379,2380],{},"Progressive Web App (PWA)",[117,2382,2383],{},[110,2384,2385],{},"Optionally, turn the web app into a PWA for better mobile experience.",[1017,2387,2389],{"id":2388},"_15-content-management-system-cms-for-admins",[113,2390,2391],{},"15. Content Management System (CMS) for Admins",[117,2393,2394,2404],{},[110,2395,2396,265,2399],{},[113,2397,2398],{},"Static Page Management",[117,2400,2401],{},[110,2402,2403],{},"Allow admins to manage static pages like Terms and Conditions, About Us, etc.",[110,2405,2406,265,2409],{},[113,2407,2408],{},"Announcements",[117,2410,2411],{},[110,2412,2413],{},"Admins can make platform-wide announcements (e.g., new features, updates).",[1017,2415,2417],{"id":2416},"_16-third-party-integrations",[113,2418,2419],{},"16. Third-Party Integrations",[117,2421,2422,2432,2442],{},[110,2423,2424,265,2427],{},[113,2425,2426],{},"Social Media Integration",[117,2428,2429],{},[110,2430,2431],{},"Allow users to sign up and log in using their Google, Facebook, or LinkedIn accounts.",[110,2433,2434,265,2437],{},[113,2435,2436],{},"Analytics Tools",[117,2438,2439],{},[110,2440,2441],{},"Integrate Google Analytics or other analytics tools to track user behavior and engagement.",[110,2443,2444,265,2447],{},[113,2445,2446],{},"Email Marketing",[117,2448,2449],{},[110,2450,2451],{},"Integrate tools like Mailchimp for sending newsletters and updates.",[1014,2453],{},[1017,2455,2457],{"id":2456},"optional-advanced-features",[113,2458,2459],{},"Optional Advanced Features",[107,2461,2462,2472,2482,2492,2502,2512],{},[110,2463,2464,265,2467],{},[113,2465,2466],{},"Gamification",[117,2468,2469],{},[110,2470,2471],{},"Introduce a points-based system for students to earn points and rewards as they complete courses or perform well in quizzes.",[110,2473,2474,265,2477],{},[113,2475,2476],{},"AI-Powered Course Recommendations",[117,2478,2479],{},[110,2480,2481],{},"Use machine learning algorithms to suggest courses based on a student’s previous activity or interests.",[110,2483,2484,265,2487],{},[113,2485,2486],{},"Multilingual Support",[117,2488,2489],{},[110,2490,2491],{},"Implement multi-language support to cater to students from different regions.",[110,2493,2494,265,2497],{},[113,2495,2496],{},"Plagiarism Detection",[117,2498,2499],{},[110,2500,2501],{},"Use third-party plagiarism detection tools to check student assignments for originality.",[110,2503,2504,265,2507],{},[113,2505,2506],{},"Offline Mode",[117,2508,2509],{},[110,2510,2511],{},"Allow students to download course content for offline access.",[110,2513,2514,265,2517],{},[113,2515,2516],{},"Video Playback with Annotations",[117,2518,2519],{},[110,2520,2521],{},"Allow students to annotate videos while watching lectures and save their notes for future reference.",[1014,2523],{},[1017,2525,2527],{"id":2526},"summary",[113,2528,2529],{},"Summary",[88,2531,2532],{},"A full-featured LMS using the MERN stack can include a wide array of features ranging from basic user management and course delivery to advanced features like real-time chat, video conferencing, and gamification. The extent of features will depend on your project scope, but the features listed above will give you a comprehensive, scalable, and user-friendly LMS.",{"title":165,"searchDepth":206,"depth":206,"links":2534},[2535,2536,2537,2538,2539,2540,2541,2542,2543,2544,2545,2546,2547,2548,2549,2550,2551,2552,2553,2554,2555,2556,2557,2558],{"id":1091,"depth":212,"text":1094},{"id":1137,"depth":212,"text":1140},{"id":1367,"depth":212,"text":1370},{"id":1478,"depth":212,"text":1481},{"id":1706,"depth":212,"text":1709},{"id":1008,"depth":212,"text":1009},{"id":1771,"depth":212,"text":1774},{"id":1838,"depth":212,"text":1841},{"id":1916,"depth":212,"text":1919},{"id":1964,"depth":212,"text":1967},{"id":2029,"depth":212,"text":2032},{"id":2067,"depth":212,"text":2070},{"id":2105,"depth":212,"text":2108},{"id":2153,"depth":212,"text":2156},{"id":2191,"depth":212,"text":2194},{"id":2239,"depth":212,"text":2242},{"id":2277,"depth":212,"text":2280},{"id":2305,"depth":212,"text":2308},{"id":2333,"depth":212,"text":2336},{"id":2360,"depth":212,"text":2363},{"id":2388,"depth":212,"text":2391},{"id":2416,"depth":212,"text":2419},{"id":2456,"depth":212,"text":2459},{"id":2526,"depth":212,"text":2529},"2025-08-24","Create a Learning Management System using MERN stack with course management, user roles, authentication, and file storage in this comprehensive guide","https:\u002F\u002Fcdn.hashnode.com\u002Fres\u002Fhashnode\u002Fimage\u002Fupload\u002Fv1756738480908\u002Fd3df4aab-3170-4d1c-b4ec-a3eb1858f84a.png",{},{"title":34,"description":2560},"2WHN9nr3VATFADeAnh8tYqq4jAq9oOc7osUbDOeBYhA",{"id":2566,"title":14,"author":2567,"body":2570,"date":2945,"description":2946,"extension":1069,"image":2947,"meta":2948,"minRead":212,"navigation":73,"path":15,"seo":2949,"stem":16,"__hash__":2950},"blog\u002Fblog\u002Fbackground-dns-refresh-in-nodejs-keeping-cached-ips-updated.md",{"name":80,"to":67,"username":81,"avatar":2568},{"src":2569,"alt":80},"https:\u002F\u002Fcdn.hashnode.com\u002Fres\u002Fhashnode\u002Fimage\u002Fupload\u002Fv1733546636355\u002Fa4ae8ce5-eb4c-4ed8-ab9d-15a4b8f32ea5.png?w=500&h=500&fit=crop&crop=entropy&auto=compress,format&format=webp",{"type":85,"value":2571,"toc":2923},[2572,2577,2586,2589,2592,2609,2612,2616,2620,2623,2637,2641,2648,2655,2662,2669,2673,2676,2680,2683,2700,2704,2711,2717,2723,2729,2733,2737,2743,2747,2753,2757,2763,2767,2848,2852,2884,2888,2891,2895,2909,2913,2916],[2573,2574,2576],"h2",{"id":2575},"introduction","📌 Introduction",[88,2578,2579,2580,2585],{},"In our previous blog on ",[1026,2581,2584],{"href":2582,"rel":2583},"https:\u002F\u002Fsachinthapa.hashnode.dev\u002Fdns-caching-in-nodejs-why-and-how-to-optimize-api-performance",[1030],"DNS Caching in Node.js",", we learned how caching resolved IP addresses improves performance and prevents frequent DNS lookups. However, one challenge remains: what happens when the TTL expires?",[88,2587,2588],{},"With basic DNS caching, when an IP address expires, our system performs a fresh DNS lookup before serving the next request. This creates latency spikes and blocking operations for users. To fix this problem, we need to implement background DNS refresh.",[88,2590,2591],{},"In this comprehensive guide, we'll cover:",[117,2593,2594,2597,2600,2603,2606],{},[110,2595,2596],{},"Why background DNS refresh is essential for consistent performance",[110,2598,2599],{},"How it eliminates latency spikes in high-traffic applications",[110,2601,2602],{},"A detailed implementation in Node.js with scheduled updates",[110,2604,2605],{},"Performance benchmarks comparing on-demand and background refresh",[110,2607,2608],{},"Best practices for production environments",[88,2610,2611],{},"By the end, you'll know how to keep cached DNS records fresh without ever blocking requests! 🚀",[2573,2613,2615],{"id":2614},"the-problem-delayed-dns-refresh-causes-latency-spikes","❌ The Problem: Delayed DNS Refresh Causes Latency Spikes",[1017,2617,2619],{"id":2618},"how-basic-dns-caching-works","🔹 How Basic DNS Caching Works",[88,2621,2622],{},"With the basic caching approach:",[107,2624,2625,2628,2631,2634],{},[110,2626,2627],{},"We resolve a domain name to an IP and cache it until the TTL expires",[110,2629,2630],{},"After TTL expiration, the next request triggers a fresh DNS lookup",[110,2632,2633],{},"That specific request experiences a delay while waiting for DNS resolution",[110,2635,2636],{},"Subsequent requests are fast again until the next expiration",[1017,2638,2640],{"id":2639},"issues-with-this-approach","🔹 Issues with This Approach",[88,2642,2643,2644,2647],{},"1️⃣ ",[113,2645,2646],{},"Latency Spikes"," – Users experience periodic delays when an expired cache is refreshed",[88,2649,2650,2651,2654],{},"2️⃣ ",[113,2652,2653],{},"Blocking Requests"," – The first request after expiration waits for a DNS resolution",[88,2656,2657,2658,2661],{},"3️⃣ ",[113,2659,2660],{},"Inconsistent Performance"," – Some requests are fast (cached IPs), others are slow (DNS lookups)",[88,2663,2664,2665,2668],{},"4️⃣ ",[113,2666,2667],{},"Poor User Experience"," – Unpredictable response times frustrate users",[2573,2670,2672],{"id":2671},"solution-refresh-ips-in-the-background","📌 Solution: Refresh IPs in the Background",[88,2674,2675],{},"Instead of waiting for a request to trigger a DNS resolution, we can refresh cached IPs proactively before they expire. This ensures continuous smooth operation without any request ever having to wait.",[2573,2677,2679],{"id":2678},"the-solution-implementing-background-dns-refresh","✅ The Solution: Implementing Background DNS Refresh",[88,2681,2682],{},"Our improved approach:",[107,2684,2685,2688,2691,2694,2697],{},[110,2686,2687],{},"Resolve and cache DNS records as before",[110,2689,2690],{},"Calculate when to refresh (typically at 80% of TTL)",[110,2692,2693],{},"Schedule background refresh tasks before TTL expiry",[110,2695,2696],{},"Keep cached records updated without impacting incoming requests",[110,2698,2699],{},"Ensure consistent performance for all users",[1017,2701,2703],{"id":2702},"benefits-of-background-dns-refresh","🔥 Benefits of Background DNS Refresh:",[88,2705,2706,2707,2710],{},"✅ ",[113,2708,2709],{},"Eliminates latency spikes"," – Requests never wait for DNS lookups",[88,2712,2706,2713,2716],{},[113,2714,2715],{},"Prevents blocking requests"," – Cached IPs are always ready before expiration",[88,2718,2706,2719,2722],{},[113,2720,2721],{},"Smoothens API performance"," – Provides consistent, predictable response times",[88,2724,2706,2725,2728],{},[113,2726,2727],{},"Handles DNS failures gracefully"," – If a refresh fails, the existing IP remains valid",[2573,2730,2732],{"id":2731},"implementation-background-dns-refresh-in-nodejs","🔧 Implementation: Background DNS Refresh in Node.js",[1017,2734,2736],{"id":2735},"step-1-enhance-the-cache-to-support-background-refresh","Step 1: Enhance the Cache to Support Background Refresh",[160,2738,2741],{"className":2739,"code":2740,"language":1500},[1498],"import express from \"express\";\nimport fetch from \"node-fetch\";\nimport * as dns from \"node:dns\";\n\nconst app = express();\nconst PORT = 3000;\nconst dnsCache = new Map();\nconst REFRESH_THRESHOLD = 0.8; \u002F\u002F Refresh when 80% of TTL has elapsed\n\nasync function resolveAndCache(domain) {\n    try {\n        const result = await dns.promises.resolve4(domain, { ttl: true });\n        if (result.length > 0) {\n            const { address, ttl } = result[0];\n            const now = Date.now();\n            const expiresAt = now + ttl * 1000;\n\n            \u002F\u002F Calculate when to refresh (before expiry)\n            const refreshAt = now + (ttl * REFRESH_THRESHOLD * 1000);\n\n            \u002F\u002F Store both expiry and refresh times\n            dnsCache.set(domain, { \n                address, \n                expiresAt,\n                refreshAt\n            });\n\n            console.log(`Updated IP for ${domain}: ${address} (TTL: ${ttl}s)`);\n\n            \u002F\u002F Schedule background refresh before expiry\n            const timeUntilRefresh = refreshAt - now;\n            setTimeout(() => refreshDomain(domain), timeUntilRefresh);\n\n            return address;\n        }\n    } catch (error) {\n        console.error(`DNS lookup failed for ${domain}:`, error);\n    }\n    return null;\n}\n",[123,2742,2740],{"__ignoreMap":165},[1017,2744,2746],{"id":2745},"step-2-set-up-a-background-refresh-process","Step 2: Set Up a Background Refresh Process",[160,2748,2751],{"className":2749,"code":2750,"language":1500},[1498],"async function refreshDomain(domain) {\n    console.log(`Background refresh for ${domain}`);\n    const cached = dnsCache.get(domain);\n\n    \u002F\u002F Only refresh if this entry still exists and hasn't been refreshed already\n    if (cached && Date.now() \u003C cached.expiresAt && Date.now() >= cached.refreshAt) {\n        await resolveAndCache(domain);\n    }\n}\n\n\u002F\u002F Function to get a cached IP (with fallback to resolution)\nasync function getCachedIP(domain) {\n    const now = Date.now();\n\n    \u002F\u002F Check if domain is cached and still valid\n    if (dnsCache.has(domain)) {\n        const { address, expiresAt } = dnsCache.get(domain);\n        if (now \u003C expiresAt) {\n            return address;\n        }\n    }\n\n    \u002F\u002F Not cached or expired, resolve now\n    return await resolveAndCache(domain);\n}\n\n\u002F\u002F Initialize cache for commonly used domains\nasync function initializeCache(domains) {\n    for (const domain of domains) {\n        await resolveAndCache(domain);\n    }\n}\n\nconst monitoredDomains = [\"api.weather.com\", \"api.payments.com\", \"api.auth.com\"];\ninitializeCache(monitoredDomains);\n",[123,2752,2750],{"__ignoreMap":165},[1017,2754,2756],{"id":2755},"step-3-use-cached-ips-without-delays","Step 3: Use Cached IPs Without Delays",[160,2758,2761],{"className":2759,"code":2760,"language":1500},[1498],"app.get(\"\u002Fweather\", async (req, res) => {\n    const weatherAPI = \"api.weather.com\";\n    try {\n        const ip = await getCachedIP(weatherAPI);\n        if (!ip) return res.status(500).json({ error: \"DNS lookup failed\" });\n\n        const response = await fetch(`http:\u002F\u002F${ip}\u002Fdata`, {\n            headers: { 'Host': 'api.weather.com' }\n        });\n\n        const data = await response.json();\n        res.json({ success: true, data });\n    } catch (error) {\n        res.status(500).json({ error: \"Failed to fetch weather data\" });\n    }\n});\n\napp.listen(PORT, () => console.log(`Server running on port ${PORT}`));\n",[123,2762,2760],{"__ignoreMap":165},[2573,2764,2766],{"id":2765},"performance-comparison-with-vs-without-background-refresh","⚡ Performance Comparison: With vs. Without Background Refresh",[853,2768,2769,2782],{},[856,2770,2771],{},[859,2772,2773,2776,2779],{},[862,2774,2775],{},"Feature",[862,2777,2778],{},"Without Background Refresh",[862,2780,2781],{},"With Background Refresh",[878,2783,2784,2794,2804,2815,2826,2837],{},[859,2785,2786,2788,2791],{},[883,2787,2646],{},[883,2789,2790],{},"Present (after TTL expiry)",[883,2792,2793],{},"Eliminated completely",[859,2795,2796,2798,2801],{},[883,2797,2653],{},[883,2799,2800],{},"First request after expiry is slow",[883,2802,2803],{},"No blocking requests ever",[859,2805,2806,2809,2812],{},[883,2807,2808],{},"DNS Lookup Frequency",[883,2810,2811],{},"On-demand (delayed)",[883,2813,2814],{},"Periodic (proactive)",[859,2816,2817,2820,2823],{},[883,2818,2819],{},"Performance",[883,2821,2822],{},"Inconsistent",[883,2824,2825],{},"Smooth & consistent",[859,2827,2828,2831,2834],{},[883,2829,2830],{},"p95 Latency",[883,2832,2833],{},"150-250ms",[883,2835,2836],{},"5-10ms",[859,2838,2839,2842,2845],{},[883,2840,2841],{},"p99 Latency",[883,2843,2844],{},"300-400ms",[883,2846,2847],{},"10-15ms",[2573,2849,2851],{"id":2850},"best-practices-for-production","🎯 Best Practices for Production",[107,2853,2854,2860,2866,2872,2878],{},[110,2855,2856,2859],{},[113,2857,2858],{},"Adjust the refresh threshold"," based on your application's needs (80% of TTL is a good starting point)",[110,2861,2862,2865],{},[113,2863,2864],{},"Monitor DNS resolution times"," to identify performance issues",[110,2867,2868,2871],{},[113,2869,2870],{},"Implement error handling"," in refresh operations to avoid crashing the application",[110,2873,2874,2877],{},[113,2875,2876],{},"Consider memory usage"," when caching many domains",[110,2879,2880,2883],{},[113,2881,2882],{},"Use health checks"," to ensure your background refresh process is working properly",[2573,2885,2887],{"id":2886},"final-thoughts","🚀 Final Thoughts",[88,2889,2890],{},"✅ Background DNS refresh ensures that DNS lookups never block a request, eliminating latency spikes and providing consistent performance.",[1017,2892,2894],{"id":2893},"ideal-use-cases","💡 Ideal Use Cases:",[117,2896,2897,2900,2903,2906],{},[110,2898,2899],{},"APIs handling thousands of requests per second",[110,2901,2902],{},"Microservices communicating with third-party services",[110,2904,2905],{},"IoT devices that need uninterrupted connectivity",[110,2907,2908],{},"Payment gateways, streaming services, and real-time applications",[1017,2910,2912],{"id":2911},"next-steps","🔥 Next Steps",[88,2914,2915],{},"In our next blog post, we'll explore how to implement a robust DNS fallback mechanism that can handle DNS resolution failures gracefully, ensuring your application stays operational even when DNS providers experience issues.",[88,2917,2918,2919,2922],{},"Check out our previous blog on ",[1026,2920,2584],{"href":2582,"rel":2921},[1030]," and stay tuned for our upcoming post on DNS Fallback Mechanisms! 🚀",{"title":165,"searchDepth":206,"depth":206,"links":2924},[2925,2926,2930,2931,2934,2939,2940,2941],{"id":2575,"depth":206,"text":2576},{"id":2614,"depth":206,"text":2615,"children":2927},[2928,2929],{"id":2618,"depth":212,"text":2619},{"id":2639,"depth":212,"text":2640},{"id":2671,"depth":206,"text":2672},{"id":2678,"depth":206,"text":2679,"children":2932},[2933],{"id":2702,"depth":212,"text":2703},{"id":2731,"depth":206,"text":2732,"children":2935},[2936,2937,2938],{"id":2735,"depth":212,"text":2736},{"id":2745,"depth":212,"text":2746},{"id":2755,"depth":212,"text":2756},{"id":2765,"depth":206,"text":2766},{"id":2850,"depth":206,"text":2851},{"id":2886,"depth":206,"text":2887,"children":2942},[2943,2944],{"id":2893,"depth":212,"text":2894},{"id":2911,"depth":212,"text":2912},"2025-03-11","Learn how to implement background DNS refresh in Node.js to maintain consistent performance and eliminate latency spikes in high-traffic applications","\u002Fimages\u002Fblog\u002Fbackground-dns-refresh-in-nodejs-keeping-cached-ips-updated.png",{},{"title":14,"description":2946},"o60gjFJTCvrXQkO1HOjDfxkAbQsN2sJmRQS6xjaHJ08",{"id":2952,"title":26,"author":2953,"body":2955,"date":2945,"description":3688,"extension":1069,"image":3689,"meta":3690,"minRead":233,"navigation":73,"path":27,"seo":3691,"stem":28,"__hash__":3692},"blog\u002Fblog\u002Fdns-caching-in-nodejs-supercharge-your-apps-speed-and-reliability.md",{"name":80,"to":67,"username":81,"avatar":2954},{"src":83,"alt":80},{"type":85,"value":2956,"toc":3642},[2957,2964,2966,2973,2978,2988,2995,3000,3018,3025,3027,3034,3120,3131,3137,3142,3144,3151,3156,3171,3176,3187,3189,3196,3201,3208,3214,3219,3230,3237,3243,3245,3262,3269,3273,3292,3296,3320,3322,3329,3333,3347,3351,3371,3377,3379,3386,3451,3456,3458,3464,3469,3492,3497,3499,3505,3510,3530,3536,3538,3544,3551,3571,3578,3595,3597,3604,3607,3613,3624,3629,3637],[88,2958,2959,2963],{},[2960,2961,2962],"em",{},"Ever wondered why your app feels sluggish when fetching data from APIs? The hidden culprit might be DNS lookups! Let's fix that."," 💡",[1014,2965],{},[2573,2967,2969,2970],{"id":2968},"why-should-you-care-about-dns","🎯 ",[113,2971,2972],{},"Why Should You Care About DNS?",[88,2974,2975],{},[2960,2976,2977],{},"(Spoiler: Your app's speed depends on it!)",[88,2979,2980,2981,2984,2985,361],{},"Imagine you're sending letters 📮. Every time you mail one, you first run to the post office to ask, ",[2960,2982,2983],{},"\"Hey, what's Alice's address again?\""," Sounds exhausting, right? That's exactly what your app does with ",[113,2986,2987],{},"DNS lookups",[1017,2989,2991,2992],{"id":2990},"dns-resolution-101","🔍 ",[113,2993,2994],{},"DNS Resolution 101",[88,2996,2997],{},[2960,2998,2999],{},"(In 10 seconds!)",[107,3001,3002,3009,3015],{},[110,3003,3004,3005,3008],{},"You request ",[123,3006,3007],{},"api.catfacts.com"," 🐱",[110,3010,3011,3012],{},"DNS servers translate it to an IP like ",[123,3013,3014],{},"104.18.22.34",[110,3016,3017],{},"Your app connects to that IP",[88,3019,3020,3021,3024],{},"But what if this happens ",[113,3022,3023],{},"every single request","? 😱",[1014,3026],{},[2573,3028,3030,3031],{"id":3029},"the-5-deadly-sins-of-frequent-dns-lookups","🚨 ",[113,3032,3033],{},"The 5 Deadly Sins of Frequent DNS Lookups",[853,3035,3036,3049],{},[856,3037,3038],{},[859,3039,3040,3043,3046],{},[862,3041,3042],{},"Sin",[862,3044,3045],{},"Symptom",[862,3047,3048],{},"Real-World Impact",[878,3050,3051,3064,3081,3094,3107],{},[859,3052,3053,3058,3061],{},[883,3054,3055],{},[113,3056,3057],{},"Latency Overload",[883,3059,3060],{},"\"Why is this API call taking 300ms?!\"",[883,3062,3063],{},"Users rage-quit your slow app 🐢💔",[859,3065,3066,3071,3078],{},[883,3067,3068],{},[113,3069,3070],{},"DNS Server Spam",[883,3072,3073,3074,3077],{},"Your app becomes ",[2960,3075,3076],{},"that neighbor"," who won't stop ringing doorbells",[883,3079,3080],{},"DNS providers block you 🚫",[859,3082,3083,3088,3091],{},[883,3084,3085],{},[113,3086,3087],{},"Single Point of Failure",[883,3089,3090],{},"DNS goes down → Your app crashes",[883,3092,3093],{},"Midnight outage calls 😱📉",[859,3095,3096,3101,3104],{},[883,3097,3098],{},[113,3099,3100],{},"Wasted Resources",[883,3102,3103],{},"CPU cycles burned on repetitive lookups",[883,3105,3106],{},"Cloud bill shock 💸🔥",[859,3108,3109,3114,3117],{},[883,3110,3111],{},[113,3112,3113],{},"API Rate Limiting",[883,3115,3116],{},"\"Error 429: Too Many Requests\"",[883,3118,3119],{},"Sales fail during Black Friday 🛒❌",[1017,3121,3123,3124,3127,3128],{"id":3122},"bad-code-alert-dont-do-this","👎 ",[113,3125,3126],{},"Bad Code Alert"," ",[2960,3129,3130],{},"(Don't Do This!)",[160,3132,3135],{"className":3133,"code":3134,"language":1500},[1498],"\u002F\u002F ❌ Naive implementation - DNS lookup EVERY time!\nasync function fetchCatFact() {\n  const { address } = await dns.resolve4('api.catfacts.com');\n  return fetch(`http:\u002F\u002F${address}\u002Ffact`, {\n    headers: { Host: 'api.catfacts.com' } \u002F\u002F 🚩 Redundant work!\n  });\n}\n",[123,3136,3134],{"__ignoreMap":165},[88,3138,3139],{},[2960,3140,3141],{},"This is like re-checking Alice's address for every letter!",[1014,3143],{},[2573,3145,3147,3148],{"id":3146},"dns-caching-to-the-rescue","🦸 ",[113,3149,3150],{},"DNS Caching to the Rescue!",[88,3152,3153],{},[113,3154,3155],{},"How it works:",[107,3157,3158,3161,3168],{},[110,3159,3160],{},"First request → Ask DNS for IP 📡",[110,3162,3163,3164,3167],{},"Store IP with a ",[2960,3165,3166],{},"\"Best Before\""," timestamp (TTL) ⏳",[110,3169,3170],{},"Subsequent requests → Use cached IP until expiry ♻️",[88,3172,3173],{},[113,3174,3175],{},"Benefits:",[117,3177,3178,3181,3184],{},[110,3179,3180],{},"⚡ 90%+ faster API calls",[110,3182,3183],{},"🛡️ Survives DNS outages",[110,3185,3186],{},"📉 80% fewer DNS queries",[1014,3188],{},[2573,3190,3192,3193],{"id":3191},"lets-build-a-dns-cache-in-nodejs","👨💻 ",[113,3194,3195],{},"Let's Build a DNS Cache in Node.js!",[88,3197,3198],{},[2960,3199,3200],{},"(Code Walkthrough for Humans)",[1017,3202,3204,3205],{"id":3203},"step-1-create-a-smart-cache","🧠 ",[113,3206,3207],{},"Step 1: Create a Smart Cache",[160,3209,3212],{"className":3210,"code":3211,"language":1500},[1498],"\u002F\u002F Our cache: { domain: { address: '1.2.3.4', expiresAt: 169876543210 } }\nconst dnsCache = new Map(); \n\nasync function getCachedIP(domain) {\n  const now = Date.now();\n\n  \u002F\u002F 🕵️ Check cache first\n  if (dnsCache.has(domain)) {\n    const { address, expiresAt } = dnsCache.get(domain);\n    if (now \u003C expiresAt) {\n      console.log(`✨ Using cached IP for ${domain}: ${address}`);\n      return address; \u002F\u002F Cache hit!\n    }\n  }\n\n  \u002F\u002F 🆕 DNS lookup when cache misses\u002Fexpires\n  try {\n    const records = await dns.promises.resolve4(domain, { ttl: true });\n    if (records.length === 0) throw new Error('No records found');\n\n    const { address, ttl } = records[0];\n    const expiresAt = now + ttl * 1000; \u002F\u002F TTL is in seconds\n\n    dnsCache.set(domain, { address, expiresAt });\n    console.log(`🚀 Fetched fresh IP for ${domain}: ${address} (TTL: ${ttl}s)`);\n    return address;\n  } catch (error) {\n    console.error(`🔥 DNS failed for ${domain}:`, error.message);\n    return null; \u002F\u002F Graceful degradation\n  }\n}\n",[123,3213,3211],{"__ignoreMap":165},[88,3215,3216],{},[113,3217,3218],{},"Key Features:",[117,3220,3221,3224,3227],{},[110,3222,3223],{},"Automatic TTL handling ⏰",[110,3225,3226],{},"Error logging for debugging 🐞",[110,3228,3229],{},"Graceful failure handling 🤗",[1017,3231,3233,3234],{"id":3232},"step-2-supercharged-api-endpoint","🚀 ",[113,3235,3236],{},"Step 2: Supercharged API Endpoint",[160,3238,3241],{"className":3239,"code":3240,"language":1500},[1498],"app.get('\u002Fcat-fact', async (req, res) => {\n  try {\n    const ip = await getCachedIP('api.catfacts.com');\n    if (!ip) throw new Error('DNS unavailable');\n\n    \u002F\u002F 🎯 Magic: Use IP but keep 'Host' header!\n    const response = await fetch(`http:\u002F\u002F${ip}\u002Ffact`, {\n      headers: { Host: 'api.catfacts.com' } \u002F\u002F Required for SSL\u002FSNI\n    });\n\n    const fact = await response.json();\n    res.json({ fact });\n  } catch (error) {\n    res.status(500).json({ error: \"Failed to fetch cat facts 😿\" });\n  }\n});\n",[123,3242,3240],{"__ignoreMap":165},[1014,3244],{},[88,3246,3247,3250,3253,3254,3257,3258,3261],{},[113,3248,3249],{},"Wait, why the Host header?",[3251,3252],"br",{},"\nModern servers host multiple sites on one IP. The ",[123,3255,3256],{},"Host"," header tells them ",[2960,3259,3260],{},"\"I want catfacts.com, not dogmemes.com!\""," 🐶≠🐱",[2573,3263,3265,3266],{"id":3264},"ttl-time-to-live-deep-dive","⏳ ",[113,3267,3268],{},"TTL (Time-To-Live) Deep Dive",[1017,3270,3272],{"id":3271},"what-developers-often-miss","What Developers Often Miss:",[117,3274,3275,3282],{},[110,3276,3277,3278,3281],{},"TTL is set by the ",[113,3279,3280],{},"DNS provider",", not your app (e.g., Cloudflare defaults to 300s).",[110,3283,3284,3287,3288,3291],{},[113,3285,3286],{},"Stale Cache Risks",": Using expired IPs can lead to ",[123,3289,3290],{},"ENOTFOUND"," errors.",[1017,3293,3295],{"id":3294},"best-practices","Best Practices:",[107,3297,3298,3304,3314],{},[110,3299,3300,3303],{},[113,3301,3302],{},"Respect TTL",": Never override it—providers rotate IPs for load balancing.",[110,3305,3306,3309,3310,3313],{},[113,3307,3308],{},"Buffer Refresh",": Refresh cache at ",[123,3311,3312],{},"TTL - 10%"," to avoid stale entries.",[110,3315,3316,3319],{},[113,3317,3318],{},"Fallback Mechani sm",": If cached IP fails, retry with fresh DNS.",[1014,3321],{},[2573,3323,3325,3326],{"id":3324},"️-security-considerations-for-dns-caching","🛡️ ",[113,3327,3328],{},"Security Considerations for DNS Caching",[1017,3330,3332],{"id":3331},"risks","Risks:",[107,3334,3335,3341],{},[110,3336,3337,3340],{},[113,3338,3339],{},"Cache Poisoning",": Malicious actors inject fake DNS records.",[110,3342,3343,3346],{},[113,3344,3345],{},"Stale IPs",": Expired entries pointing to decommissioned servers.",[1017,3348,3350],{"id":3349},"mitigations","Mitigations:",[117,3352,3353,3359,3365],{},[110,3354,3355,3358],{},[113,3356,3357],{},"DNSSEC Validation",": Ensure DNS responses are digitally signed.",[110,3360,3361,3364],{},[113,3362,3363],{},"TTL Sanity Checks",": Reject TTLs > 1 hour (common in attacks).",[110,3366,3367,3370],{},[113,3368,3369],{},"Isolate Caches",": Use separate caches per environment (prod vs. dev).",[160,3372,3375],{"className":3373,"code":3374,"language":1500},[1498],"\u002F\u002F Example: DNSSEC Validation (using external library)\nimport { validateDNSSEC } from 'dnssec-validator';\n\nasync function secureResolve(domain) {\n  const records = await dns.resolve4(domain, { ttl: true });\n  const isValid = await validateDNSSEC(domain, records);\n  if (!isValid) throw new Error('DNSSEC validation failed');\n  return records;\n}\n",[123,3376,3374],{"__ignoreMap":165},[1014,3378],{},[2573,3380,3382,3383],{"id":3381},"performance-showdown-cache-vs-no-cache","📊 ",[113,3384,3385],{},"Performance Showdown: Cache vs No Cache",[853,3387,3388,3407],{},[856,3389,3390],{},[859,3391,3392,3395,3398,3401,3404],{},[862,3393,3394],{},"Scenario",[862,3396,3397],{},"100 Requests",[862,3399,3400],{},"Latency",[862,3402,3403],{},"DNS Queries",[862,3405,3406],{},"Risk of Failure",[878,3408,3409,3426],{},[859,3410,3411,3414,3417,3420,3423],{},[883,3412,3413],{},"No Cache",[883,3415,3416],{},"1.2 sec\u002Freq",[883,3418,3419],{},"12,000ms",[883,3421,3422],{},"100",[883,3424,3425],{},"High 😰",[859,3427,3428,3433,3438,3443,3448],{},[883,3429,3430],{},[113,3431,3432],{},"With Cache",[883,3434,3435],{},[113,3436,3437],{},"0.3 sec\u002Freq",[883,3439,3440],{},[113,3441,3442],{},"300ms",[883,3444,3445],{},[113,3446,3447],{},"1",[883,3449,3450],{},"Low 😎",[88,3452,3453],{},[2960,3454,3455],{},"Results from testing a weather API endpoint (AWS t3.micro)",[1014,3457],{},[2573,3459,3030,3461],{"id":3460},"when-not-to-cache-dns",[113,3462,3463],{},"When NOT to Cache DNS",[88,3465,3466],{},[2960,3467,3468],{},"(Yes, there are exceptions!)",[117,3470,3471,3478,3485],{},[110,3472,3473,3474,3477],{},"🕵️‍♂️ ",[113,3475,3476],{},"Dynamic IPs",": Some APIs rotate IPs frequently",[110,3479,3480,3481,3484],{},"🌍 ",[113,3482,3483],{},"Geo-DNS",": IPs change based on user location",[110,3486,3487,3488,3491],{},"🔄 ",[113,3489,3490],{},"Load Balancers",": IPs might point to different servers",[88,3493,3494],{},[2960,3495,3496],{},"Always check your API provider's DNS behavior!",[1014,3498],{},[2573,3500,3233,3502],{"id":3501},"take-it-to-production",[113,3503,3504],{},"Take It to Production!",[88,3506,3507],{},[113,3508,3509],{},"Pro Tips:",[107,3511,3512,3518,3524],{},[110,3513,3514,3517],{},[113,3515,3516],{},"Background Refresh",": Update cached IPs 5 mins before TTL expires",[110,3519,3520,3523],{},[113,3521,3522],{},"Fallback Mechanism",": If cached IP fails, retry with fresh DNS",[110,3525,3526,3529],{},[113,3527,3528],{},"Monitoring",": Track cache hit ratio and DNS failures (Prometheus\u002FGrafana)",[160,3531,3534],{"className":3532,"code":3533,"language":1500},[1498],"\u002F\u002F Bonus: Auto-refresh cache 5 minutes before TTL expires\nfunction scheduleRefresh(domain, ttlSeconds) {\n  const refreshTime = (ttlSeconds - 300) * 1000; \u002F\u002F 5 mins buffer\n  setTimeout(async () => {\n    await getCachedIP(domain); \u002F\u002F Force refresh\n  }, refreshTime);\n}\n",[123,3535,3533],{"__ignoreMap":165},[1014,3537],{},[2573,3539,3480,3541],{"id":3540},"real-world-impact-case-studies",[113,3542,3543],{},"Real-World Impact: Case Studies",[1017,3545,3547,3548],{"id":3546},"_1-e-commerce-giant","1. ",[113,3549,3550],{},"E-Commerce Giant",[117,3552,3553,3559,3565],{},[110,3554,3555,3558],{},[113,3556,3557],{},"Problem",": 500ms added latency during Black Friday sales.",[110,3560,3561,3564],{},[113,3562,3563],{},"Solution",": DNS caching + TTL-aware prefetching.",[110,3566,3567,3570],{},[113,3568,3569],{},"Result",": 40% reduction in API latency; $2M+ saved in potential lost sales.",[1017,3572,3574,3575],{"id":3573},"_2-iot-platform","2. ",[113,3576,3577],{},"IoT Platform",[117,3579,3580,3585,3590],{},[110,3581,3582,3584],{},[113,3583,3557],{},": 10,000 devices polling every 30s caused DNS rate limits.",[110,3586,3587,3589],{},[113,3588,3563],{},": Edge-side caching with Cloudflare Workers.",[110,3591,3592,3594],{},[113,3593,3569],{},": DNS queries reduced by 99.9%.",[1014,3596],{},[2573,3598,3600,3601],{"id":3599},"your-turn","📣 ",[113,3602,3603],{},"Your Turn!",[88,3605,3606],{},"Ready to turbocharge your Node.js apps? Implement DNS caching today and watch your performance metrics soar! 🚀",[88,3608,3609,3612],{},[113,3610,3611],{},"Challenge",": Try adding a cache size limit (LRU cache) to prevent memory bloat!",[88,3614,3615,3616,3619,3620,3623],{},"👉 ",[113,3617,3618],{},"Up Next",": ",[2960,3621,3622],{},"\"Zero-Downtime DNS: Background Refresh & Circuit Breakers\""," – Ensure 100% uptime even during DNS storms! ⚡",[88,3625,3626,265],{},[113,3627,3628],{},"Let me know in the comments",[117,3630,3631,3634],{},[110,3632,3633],{},"Have you hit DNS-related outages before?",[110,3635,3636],{},"What other performance tricks do you use?",[88,3638,3639],{},[2960,3640,3641],{},"Keep shipping awesome stuff! 🚢",{"title":165,"searchDepth":206,"depth":206,"links":3643},[3644,3649,3654,3656,3663,3668,3673,3675,3677,3679,3686],{"id":2968,"depth":206,"text":3645,"children":3646},"🎯 Why Should You Care About DNS?",[3647],{"id":2990,"depth":212,"text":3648},"🔍 DNS Resolution 101",{"id":3029,"depth":206,"text":3650,"children":3651},"🚨 The 5 Deadly Sins of Frequent DNS Lookups",[3652],{"id":3122,"depth":212,"text":3653},"👎 Bad Code Alert (Don't Do This!)",{"id":3146,"depth":206,"text":3655},"🦸 DNS Caching to the Rescue!",{"id":3191,"depth":206,"text":3657,"children":3658},"👨💻 Let's Build a DNS Cache in Node.js!",[3659,3661],{"id":3203,"depth":212,"text":3660},"🧠 Step 1: Create a Smart Cache",{"id":3232,"depth":212,"text":3662},"🚀 Step 2: Supercharged API Endpoint",{"id":3264,"depth":206,"text":3664,"children":3665},"⏳ TTL (Time-To-Live) Deep Dive",[3666,3667],{"id":3271,"depth":212,"text":3272},{"id":3294,"depth":212,"text":3295},{"id":3324,"depth":206,"text":3669,"children":3670},"🛡️ Security Considerations for DNS Caching",[3671,3672],{"id":3331,"depth":212,"text":3332},{"id":3349,"depth":212,"text":3350},{"id":3381,"depth":206,"text":3674},"📊 Performance Showdown: Cache vs No Cache",{"id":3460,"depth":206,"text":3676},"🚨 When NOT to Cache DNS",{"id":3501,"depth":206,"text":3678},"🚀 Take It to Production!",{"id":3540,"depth":206,"text":3680,"children":3681},"🌍 Real-World Impact: Case Studies",[3682,3684],{"id":3546,"depth":212,"text":3683},"1. E-Commerce Giant",{"id":3573,"depth":212,"text":3685},"2. IoT Platform",{"id":3599,"depth":206,"text":3687},"📣 Your Turn!","Optimize API performance in Node.js with DNS caching. Learn how caching reduces latency, prevents disruptions, and enhances reliability","https:\u002F\u002Fcdn.hashnode.com\u002Fres\u002Fhashnode\u002Fimage\u002Fupload\u002Fv1741709904653\u002Fefc07a6b-2354-45d3-8810-f1b66ff11053.png",{},{"title":26,"description":3688},"MHszLAY3CjJFQGiZWkNnc5u1xbDOisS-E53R2m3XXlU",{"id":3694,"title":38,"author":3695,"body":3697,"date":4000,"description":4001,"extension":1069,"image":4002,"meta":4003,"minRead":206,"navigation":73,"path":39,"seo":4004,"stem":40,"__hash__":4005},"blog\u002Fblog\u002Fgetting-started-with-redis-elevating-your-web-applications.md",{"name":80,"to":67,"username":81,"avatar":3696},{"src":83,"alt":80},{"type":85,"value":3698,"toc":3976},[3699,3706,3708,3712,3718,3722,3754,3756,3760,3763,3795,3797,3801,3804,3808,3811,3817,3820,3826,3828,3832,3839,3845,3847,3851,3862,3868,3870,3874,3877,3883,3889,3895,3901,3908,3914,3916,3920,3923,3927,3938,3942,3950,3952,3954,3957,3962,3973],[88,3700,3701,3702,3705],{},"When building scalable web applications, data management and performance optimization become crucial aspects. One powerful tool that can help achieve this is ",[113,3703,3704],{},"Redis",". In this blog, we'll explore Redis, its advantages, how to integrate it with an Express.js application for session management, and how you can expand its usage beyond sessions.",[1014,3707],{},[2573,3709,3711],{"id":3710},"what-is-redis","What is Redis?",[88,3713,3714,3717],{},[113,3715,3716],{},"Redis (Remote Dictionary Server)"," is an open-source, in-memory data structure store that can be used as a database, cache, and message broker. It offers lightning-fast read and write operations, making it an excellent choice for real-time applications.",[1017,3719,3721],{"id":3720},"key-features-of-redis","Key Features of Redis:",[117,3723,3724,3730,3736,3742,3748],{},[110,3725,3726,3729],{},[113,3727,3728],{},"In-Memory Storage:"," Data is stored in RAM for ultra-fast access.",[110,3731,3732,3735],{},[113,3733,3734],{},"Persistence:"," Supports snapshotting and append-only file persistence.",[110,3737,3738,3741],{},[113,3739,3740],{},"Data Structures:"," Supports strings, lists, sets, sorted sets, hashes, bitmaps, and more.",[110,3743,3744,3747],{},[113,3745,3746],{},"Replication:"," Provides primary-replica replication for high availability.",[110,3749,3750,3753],{},[113,3751,3752],{},"Pub\u002FSub Messaging:"," Enables real-time messaging patterns.",[1014,3755],{},[2573,3757,3759],{"id":3758},"why-use-redis","Why Use Redis?",[88,3761,3762],{},"Redis is a great addition to web applications for several reasons:",[107,3764,3765,3771,3777,3783,3789],{},[110,3766,3767,3770],{},[113,3768,3769],{},"Performance Boost:"," Redis drastically reduces database load by caching frequently accessed data.",[110,3772,3773,3776],{},[113,3774,3775],{},"Scalability:"," It helps in handling high traffic without slowing down the application.",[110,3778,3779,3782],{},[113,3780,3781],{},"Session Management:"," Efficiently manages session data across multiple instances.",[110,3784,3785,3788],{},[113,3786,3787],{},"Rate Limiting:"," Helps in implementing rate limiting to prevent abuse.",[110,3790,3791,3794],{},[113,3792,3793],{},"Job Queues:"," Can be used to store background tasks for asynchronous processing.",[1014,3796],{},[2573,3798,3800],{"id":3799},"setting-up-redis-in-your-project","Setting Up Redis in Your Project",[88,3802,3803],{},"Let's walk through how to integrate Redis into an Express.js application for session management.",[1017,3805,3807],{"id":3806},"step-1-install-dependencies","Step 1: Install Dependencies",[88,3809,3810],{},"To begin, install Redis and the necessary libraries:",[160,3812,3815],{"className":3813,"code":3814,"language":1500},[1498],"npm install express express-session redis connect-redis\n",[123,3816,3814],{"__ignoreMap":165},[88,3818,3819],{},"If you're using TypeScript, install the types as well:",[160,3821,3824],{"className":3822,"code":3823,"language":1500},[1498],"npm install -D @types\u002Fexpress-session @types\u002Fredis\n",[123,3825,3823],{"__ignoreMap":165},[1014,3827],{},[1017,3829,3831],{"id":3830},"step-2-configure-redis-client","Step 2: Configure Redis Client",[88,3833,3834,3835,3838],{},"In your project, create a file named ",[123,3836,3837],{},"redisClient.ts"," to set up the Redis connection:",[160,3840,3843],{"className":3841,"code":3842,"language":1500},[1498],"import Redis from \"ioredis\";\n\nconst redisClient = new Redis({\n  host: \"localhost\",\n  port: 6379,\n  retryStrategy: (times) => Math.min(times * 50, 2000),\n});\n\nredisClient.on(\"connect\", () => {\n  console.log(\"Connected to Redis\");\n});\n\nredisClient.on(\"error\", (err) => {\n  console.error(\"Redis Error:\", err);\n});\n\nexport default redisClient;\n",[123,3844,3842],{"__ignoreMap":165},[1014,3846],{},[1017,3848,3850],{"id":3849},"step-3-adding-redis-to-express-session","Step 3: Adding Redis to Express Session",[88,3852,3853,3854,3857,3858,3861],{},"Update your ",[123,3855,3856],{},"app.ts"," (or ",[123,3859,3860],{},"index.ts",") file to use Redis for session storage:",[160,3863,3866],{"className":3864,"code":3865,"language":1500},[1498],"import express from \"express\";\nimport session from \"express-session\";\nimport RedisStore from \"connect-redis\";\nimport redisClient from \".\u002FredisClient\";\n\nconst app = express();\n\napp.use(\n  session({\n    store: new RedisStore({ client: redisClient }),\n    secret: \"your_secret_key\",\n    resave: false,\n    saveUninitialized: false,\n    cookie: {\n      secure: process.env.NODE_ENV === \"production\",\n      httpOnly: true,\n      maxAge: 1000 * 60 * 60 * 24, \u002F\u002F 1 day\n    },\n  })\n);\n\napp.get(\"\u002F\", (req, res) => {\n  req.session.views = (req.session.views || 0) + 1;\n  res.send(`Number of views: ${req.session.views}`);\n});\n\napp.listen(3000, () => {\n  console.log(\"Server is running on port 3000\");\n});\n",[123,3867,3865],{"__ignoreMap":165},[1014,3869],{},[2573,3871,3873],{"id":3872},"additional-use-cases-for-redis","Additional Use Cases for Redis",[88,3875,3876],{},"Beyond session storage, Redis can be used for various other functionalities, such as:",[1017,3878,3547,3880],{"id":3879},"_1-caching-api-responses",[113,3881,3882],{},"Caching API Responses",[160,3884,3887],{"className":3885,"code":3886,"language":1500},[1498],"const cacheMiddleware = async (req, res, next) => {\n  const cachedData = await redisClient.get(req.originalUrl);\n  if (cachedData) {\n    return res.send(JSON.parse(cachedData));\n  }\n  next();\n};\n",[123,3888,3886],{"__ignoreMap":165},[1017,3890,3574,3892],{"id":3891},"_2-rate-limiting",[113,3893,3894],{},"Rate Limiting",[160,3896,3899],{"className":3897,"code":3898,"language":1500},[1498],"const rateLimitMiddleware = async (req, res, next) => {\n  const ip = req.ip;\n  const requests = await redisClient.incr(ip);\n  if (requests > 10) {\n    return res.status(429).send(\"Too many requests\");\n  }\n  await redisClient.expire(ip, 60);\n  next();\n};\n",[123,3900,3898],{"__ignoreMap":165},[1017,3902,3904,3905],{"id":3903},"_3-pubsub-messaging","3. ",[113,3906,3907],{},"Pub\u002FSub Messaging",[160,3909,3912],{"className":3910,"code":3911,"language":1500},[1498],"const publisher = redisClient;\nconst subscriber = redisClient.duplicate();\n\nsubscriber.subscribe(\"notifications\");\nsubscriber.on(\"message\", (channel, message) => {\n  console.log(`Received message: ${message} on channel ${channel}`);\n});\n\npublisher.publish(\"notifications\", \"New user signed up\");\n",[123,3913,3911],{"__ignoreMap":165},[1014,3915],{},[2573,3917,3919],{"id":3918},"pros-and-cons-of-using-redis","Pros and Cons of Using Redis",[88,3921,3922],{},"While Redis offers numerous benefits, it does have some downsides that should be considered:",[1017,3924,3926],{"id":3925},"advantages","Advantages:",[117,3928,3929,3932,3935],{},[110,3930,3931],{},"Lightning-fast performance.",[110,3933,3934],{},"Simple to integrate with multiple frameworks.",[110,3936,3937],{},"Reduces the load on primary databases.",[1017,3939,3941],{"id":3940},"disadvantages","Disadvantages:",[117,3943,3944,3947],{},[110,3945,3946],{},"Data loss possible if persistence isn't configured correctly.",[110,3948,3949],{},"Consumes RAM quickly if not managed properly.",[1014,3951],{},[2573,3953,1009],{"id":1008},[88,3955,3956],{},"Integrating Redis into your application provides numerous advantages, from enhanced performance to better scalability. While we've focused on session management here, Redis can be utilized for caching, rate limiting, job queues, and more.",[88,3958,3959],{},[113,3960,3961],{},"Next Steps:",[117,3963,3964,3967,3970],{},[110,3965,3966],{},"Explore Redis data persistence options.",[110,3968,3969],{},"Use Redis with message brokers for event-driven applications.",[110,3971,3972],{},"Implement Redis clustering for high availability.",[88,3974,3975],{},"Redis is just the beginning—start integrating it into different parts of your project today!",{"title":165,"searchDepth":206,"depth":206,"links":3977},[3978,3981,3982,3987,3995,3999],{"id":3710,"depth":206,"text":3711,"children":3979},[3980],{"id":3720,"depth":212,"text":3721},{"id":3758,"depth":206,"text":3759},{"id":3799,"depth":206,"text":3800,"children":3983},[3984,3985,3986],{"id":3806,"depth":212,"text":3807},{"id":3830,"depth":212,"text":3831},{"id":3849,"depth":212,"text":3850},{"id":3872,"depth":206,"text":3873,"children":3988},[3989,3991,3993],{"id":3879,"depth":212,"text":3990},"1. Caching API Responses",{"id":3891,"depth":212,"text":3992},"2. Rate Limiting",{"id":3903,"depth":212,"text":3994},"3. Pub\u002FSub Messaging",{"id":3918,"depth":206,"text":3919,"children":3996},[3997,3998],{"id":3925,"depth":212,"text":3926},{"id":3940,"depth":212,"text":3941},{"id":1008,"depth":206,"text":1009},"2025-01-22","Learn how to integrate Redis for scalable web applications, enhancing performance, session management, caching, and more with Express.js","https:\u002F\u002Fcdn.hashnode.com\u002Fres\u002Fhashnode\u002Fimage\u002Fupload\u002Fv1737568708534\u002F66c774dc-7298-433b-9cef-8ef3049532c5.png",{},{"title":38,"description":4001},"Pe2Q-0Z35gTg2a5OrkGxzBAhMvmQrLX_CRLcEDV1kSQ",{"id":4007,"title":50,"author":4008,"body":4010,"date":4356,"description":4357,"extension":1069,"image":4358,"meta":4359,"minRead":206,"navigation":73,"path":51,"seo":4360,"stem":52,"__hash__":4361},"blog\u002Fblog\u002Fsingle-sign-on-sso-implementation-using-passportjs-in-typescript.md",{"name":80,"to":67,"username":81,"avatar":4009},{"src":83,"alt":80},{"type":85,"value":4011,"toc":4339},[4012,4015,4021,4024,4032,4034,4040,4046,4049,4055,4061,4064,4070,4076,4078,4084,4090,4093,4099,4101,4107,4110,4116,4122,4125,4131,4134,4136,4142,4145,4150,4156,4158,4164,4167,4173,4179,4181,4187,4193,4199,4205,4207,4213,4216,4221,4227,4229,4235,4238,4243,4249,4256,4262,4264,4270,4277,4283,4290,4296,4298,4304,4315,4321,4323,4327,4334],[88,4013,4014],{},"Implementing Single Sign-On (SSO) in your application enhances user experience by allowing seamless access across multiple platforms with a single set of credentials. In Node.js applications, the Passport middleware simplifies this process by providing a flexible and modular authentication framework. This guide focuses on using Passport.js with TypeScript to maintain type safety and robust application architecture.",[2573,4016,4018],{"id":4017},"what-is-passportjs",[113,4019,4020],{},"What Is Passport.js?",[88,4022,4023],{},"Passport.js is an authentication middleware for Node.js that supports various strategies, including social logins (e.g., Google, GitHub) and traditional username-password mechanisms. It is modular, allowing developers to choose specific strategies as needed.",[88,4025,4026,4027,367],{},"For more details, visit ",[1026,4028,4031],{"href":4029,"rel":4030},"https:\u002F\u002Fwww.passportjs.org\u002Fdocs",[1030],"Passport.js documentation",[1014,4033],{},[2573,4035,4037],{"id":4036},"steps-to-implement-sso-in-typescript",[113,4038,4039],{},"Steps to Implement SSO in TypeScript",[1017,4041,4043],{"id":4042},"install-required-dependencies",[113,4044,4045],{},"Install Required Dependencies",[88,4047,4048],{},"Begin by installing the necessary packages:",[160,4050,4053],{"className":4051,"code":4052,"language":1500},[1498],"npm install passport passport-google-oauth20 passport-github2 express-session @types\u002Fpassport @types\u002Fexpress-session\n",[123,4054,4052],{"__ignoreMap":165},[1017,4056,4058],{"id":4057},"initialize-passport",[113,4059,4060],{},"Initialize Passport",[88,4062,4063],{},"Configure Passport and session handling in your application.",[92,4065,4067],{"id":4066},"code-implementation",[113,4068,4069],{},"Code Implementation:",[160,4071,4074],{"className":4072,"code":4073,"language":1500},[1498],"import express, { Application, Request, Response, NextFunction } from \"express\";\nimport session from \"express-session\";\nimport passport from \"passport\";\n\nconst app: Application = express();\n\napp.use(\n  session({\n    secret: \"your_secret_key\",\n    resave: false,\n    saveUninitialized: true,\n  })\n);\n\napp.use(passport.initialize());\napp.use(passport.session());\n",[123,4075,4073],{"__ignoreMap":165},[1014,4077],{},[1017,4079,4081],{"id":4080},"configure-authentication-strategies",[113,4082,4083],{},"Configure Authentication Strategies",[92,4085,4087],{"id":4086},"google-strategy",[113,4088,4089],{},"Google Strategy",[88,4091,4092],{},"To authenticate users via Google, configure the Google strategy:",[160,4094,4097],{"className":4095,"code":4096,"language":1500},[1498],"import { Strategy as GoogleStrategy } from \"passport-google-oauth20\";\n\npassport.use(\n  new GoogleStrategy(\n    {\n      clientID: process.env.GOOGLE_CLIENT_ID!,\n      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n      callbackURL: \"http:\u002F\u002Flocalhost:3000\u002Fauth\u002Fgoogle\u002Fcallback\",\n    },\n    async (\n      accessToken: string,\n      refreshToken: string,\n      profile: any,\n      done: Function\n    ) => {\n      try {\n        \u002F\u002F Replace with your DB logic\n        const user = await findOrCreateUser({ googleId: profile.id });\n        return done(null, user);\n      } catch (error) {\n        return done(error);\n      }\n    }\n  )\n);\n",[123,4098,4096],{"__ignoreMap":165},[1014,4100],{},[92,4102,4104],{"id":4103},"github-strategy",[113,4105,4106],{},"GitHub Strategy",[88,4108,4109],{},"Similarly, configure the GitHub strategy:",[160,4111,4114],{"className":4112,"code":4113,"language":1500},[1498],"import { Strategy as GitHubStrategy } from \"passport-github2\";\n\npassport.use(\n  new GitHubStrategy(\n    {\n      clientID: process.env.GITHUB_CLIENT_ID!,\n      clientSecret: process.env.GITHUB_CLIENT_SECRET!,\n      callbackURL: \"http:\u002F\u002Flocalhost:3000\u002Fauth\u002Fgithub\u002Fcallback\",\n    },\n    async (\n      accessToken: string,\n      refreshToken: string,\n      profile: any,\n      done: Function\n    ) => {\n      try {\n        \u002F\u002F Replace with your DB logic\n        const user = await findOrCreateUser({ githubId: profile.id });\n        return done(null, user);\n      } catch (error) {\n        return done(error);\n      }\n    }\n  )\n);\n",[123,4115,4113],{"__ignoreMap":165},[1017,4117,4119],{"id":4118},"local-strategy",[113,4120,4121],{},"Local Strategy",[88,4123,4124],{},"For applications that require traditional username and password authentication, Passport.js provides the Local Strategy:",[160,4126,4129],{"className":4127,"code":4128,"language":1500},[1498],"import { Strategy as LocalStrategy } from \"passport-local\";\n\npassport.use(\n  new LocalStrategy((username, password, done) => {\n    \u002F\u002F Replace with your user retrieval and password verification logic\n    const user = getUserByUsername(username);\n    if (!user || !verifyPassword(user, password)) {\n      return done(null, false, { message: \"Invalid credentials\" });\n    }\n    return done(null, user);\n  })\n);\n",[123,4130,4128],{"__ignoreMap":165},[88,4132,4133],{},"By utilizing the Local Strategy, you can manage authentication within your application without relying on external providers. This approach is beneficial when you prefer to handle user credentials directly.",[1014,4135],{},[1017,4137,4139],{"id":4138},"serialization-and-deserialization",[113,4140,4141],{},"Serialization and Deserialization",[88,4143,4144],{},"Handle session serialization and deserialization to maintain user sessions.",[92,4146,4148],{"id":4147},"code-implementation-1",[113,4149,4069],{},[160,4151,4154],{"className":4152,"code":4153,"language":1500},[1498],"passport.serializeUser((user: any, done: Function) => {\n  done(null, user.id);\n});\n\npassport.deserializeUser(async (id: string, done: Function) => {\n  try {\n    const user = await findUserById(id); \u002F\u002F Replace with your DB logic\n    done(null, user);\n  } catch (error) {\n    done(error);\n  }\n});\n",[123,4155,4153],{"__ignoreMap":165},[1014,4157],{},[1017,4159,4161],{"id":4160},"define-routes",[113,4162,4163],{},"Define Routes",[88,4165,4166],{},"Set up routes for initiating authentication and handling callbacks.",[92,4168,4170],{"id":4169},"google-authentication-routes",[113,4171,4172],{},"Google Authentication Routes:",[160,4174,4177],{"className":4175,"code":4176,"language":1500},[1498],"app.get(\n  \"\u002Fauth\u002Fgoogle\",\n  passport.authenticate(\"google\", { scope: [\"profile\", \"email\"] })\n);\n\napp.get(\n  \"\u002Fauth\u002Fgoogle\u002Fcallback\",\n  passport.authenticate(\"google\", { failureRedirect: \"\u002F\" }),\n  (req: Request, res: Response) => {\n    res.redirect(\"\u002Fdashboard\");\n  }\n);\n",[123,4178,4176],{"__ignoreMap":165},[1014,4180],{},[92,4182,4184],{"id":4183},"github-authentication-routes",[113,4185,4186],{},"GitHub Authentication Routes:",[160,4188,4191],{"className":4189,"code":4190,"language":1500},[1498],"app.get(\n  \"\u002Fauth\u002Fgithub\",\n  passport.authenticate(\"github\", { scope: [\"user:email\"] })\n);\n\napp.get(\n  \"\u002Fauth\u002Fgithub\u002Fcallback\",\n  passport.authenticate(\"github\", { failureRedirect: \"\u002F\" }),\n  (req: Request, res: Response) => {\n    res.redirect(\"\u002Fdashboard\");\n  }\n);\n",[123,4192,4190],{"__ignoreMap":165},[1017,4194,4196],{"id":4195},"local-authentication-routes",[113,4197,4198],{},"Local Authentication Routes:",[160,4200,4203],{"className":4201,"code":4202,"language":1500},[1498],"app.post(\n  \"\u002Flogin\",\n  passport.authenticate(\"local\", { failureRedirect: \"\u002F\" }),\n  (req: Request, res: Response) => {\n    res.redirect(\"\u002Fdashboard\");\n  }\n);\n",[123,4204,4202],{"__ignoreMap":165},[1014,4206],{},[1017,4208,4210],{"id":4209},"handle-user-logout",[113,4211,4212],{},"Handle User Logout",[88,4214,4215],{},"Allow users to log out and securely end their sessions.",[92,4217,4219],{"id":4218},"code-implementation-2",[113,4220,4069],{},[160,4222,4225],{"className":4223,"code":4224,"language":1500},[1498],"app.get(\"\u002Flogout\", (req: Request, res: Response, next: NextFunction) => {\n  req.logout((err) => {\n    if (err) {\n      return next(err);\n    }\n    res.redirect(\"\u002F\");\n  });\n});\n",[123,4226,4224],{"__ignoreMap":165},[1014,4228],{},[1017,4230,4232],{"id":4231},"handling-user-authentication",[113,4233,4234],{},"Handling User Authentication",[88,4236,4237],{},"Protect routes that require authentication by checking if the user is authenticated, implementing a middleware function.",[92,4239,4241],{"id":4240},"code-implementation-3",[113,4242,4069],{},[160,4244,4247],{"className":4245,"code":4246,"language":1500},[1498],"function isAuthenticated(req: Request, res: Response, next: NextFunction) {\n  if (req.isAuthenticated()) {\n    return next();\n  }\n  res.redirect(\"\u002F\");\n}\n",[123,4248,4246],{"__ignoreMap":165},[88,4250,4251,4252,4255],{},"Now, you can use the ",[123,4253,4254],{},"isAuthenticated"," middleware to protect routes that require authentication:",[160,4257,4260],{"className":4258,"code":4259,"language":1500},[1498],"app.get(\"\u002Fdashboard\", isAuthenticated, (req: Request, res: Response) => {\n  res.send(\"Dashboard\", { user: req.user });\n});\n",[123,4261,4259],{"__ignoreMap":165},[1014,4263],{},[1017,4265,4267],{"id":4266},"environment-variables",[113,4268,4269],{},"Environment Variables",[88,4271,4272,4273,4276],{},"Store sensitive credentials in an ",[123,4274,4275],{},".env"," file:",[160,4278,4281],{"className":4279,"code":4280,"language":1500},[1498],"GOOGLE_CLIENT_ID=your_google_client_id\nGOOGLE_CLIENT_SECRET=your_google_client_secret\nGITHUB_CLIENT_ID=your_github_client_id\nGITHUB_CLIENT_SECRET=your_github_client_secret\n",[123,4282,4280],{"__ignoreMap":165},[88,4284,4285,4286,4289],{},"Use a library like ",[123,4287,4288],{},"dotenv"," to load them:",[160,4291,4294],{"className":4292,"code":4293,"language":1500},[1498],"import dotenv from \"dotenv\";\ndotenv.config();\n",[123,4295,4293],{"__ignoreMap":165},[1014,4297],{},[1017,4299,4301],{"id":4300},"replace-placeholder-functions",[113,4302,4303],{},"Replace Placeholder Functions",[88,4305,4306,4307,4310,4311,4314],{},"Replace the placeholder functions ",[123,4308,4309],{},"findOrCreateUser"," and ",[123,4312,4313],{},"findUserById"," with actual implementations for interacting with your database:",[160,4316,4319],{"className":4317,"code":4318,"language":1500},[1498],"async function findOrCreateUser(query: object): Promise\u003Cany> {\n  \u002F\u002F Example: Interact with database to find or create user\n  return {};\n}\n\nasync function findUserById(id: string): Promise\u003Cany> {\n  \u002F\u002F Example: Fetch user by ID from database\n  return {};\n}\n",[123,4320,4318],{"__ignoreMap":165},[1014,4322],{},[2573,4324,4325],{"id":1008},[113,4326,1009],{},[88,4328,4329,4330,367],{},"By leveraging Passport.js in TypeScript, you can create a robust SSO system with type safety, flexibility, and support for multiple authentication providers like Google and GitHub. For additional configurations, refer to the ",[1026,4331,4031],{"href":4332,"rel":4333},"https:\u002F\u002Fwww.passportjs.org\u002Fdocs\u002F",[1030],[88,4335,4336],{},[2960,4337,4338],{},"Implementing SSO not only enhances user convenience but also strengthens security by centralizing authentication processes.",{"title":165,"searchDepth":206,"depth":206,"links":4340},[4341,4342,4355],{"id":4017,"depth":206,"text":4020},{"id":4036,"depth":206,"text":4039,"children":4343},[4344,4345,4346,4347,4348,4349,4350,4351,4352,4353,4354],{"id":4042,"depth":212,"text":4045},{"id":4057,"depth":212,"text":4060},{"id":4080,"depth":212,"text":4083},{"id":4118,"depth":212,"text":4121},{"id":4138,"depth":212,"text":4141},{"id":4160,"depth":212,"text":4163},{"id":4195,"depth":212,"text":4198},{"id":4209,"depth":212,"text":4212},{"id":4231,"depth":212,"text":4234},{"id":4266,"depth":212,"text":4269},{"id":4300,"depth":212,"text":4303},{"id":1008,"depth":206,"text":1009},"2025-01-17","Learn how to implement Single Sign-On (SSO) in Node.js using Passport.js and TypeScript for enhanced user experience and secure authentication","https:\u002F\u002Fcdn.hashnode.com\u002Fres\u002Fhashnode\u002Fimage\u002Fupload\u002Fv1737137497543\u002F4c9f6a01-d26c-439c-a276-50251cdae746.png",{},{"title":50,"description":4357},"30Q9zQwLo0epE_0Phzum5kAUmTiIT_EtEkC2H2zFZ_0",{"id":4363,"title":30,"author":4364,"body":4366,"date":4688,"description":4689,"extension":1069,"image":4690,"meta":4691,"minRead":233,"navigation":73,"path":31,"seo":4692,"stem":32,"__hash__":4693},"blog\u002Fblog\u002Fenhancing-redis-for-message-queues-using-external-databases-for-payload-storage.md",{"name":80,"to":67,"username":81,"avatar":4365},{"src":83,"alt":80},{"type":85,"value":4367,"toc":4679},[4368,4371,4375,4378,4389,4393,4396,4416,4420,4423,4429,4432,4458,4461,4467,4473,4476,4496,4498,4504,4508,4511,4517,4525,4531,4545,4549,4555,4558,4564,4567,4573,4576,4582,4585,4591,4594,4598,4614,4620,4626,4632,4638,4642,4674,4676],[88,4369,4370],{},"Redis is a powerful in-memory database often used for implementing message queues due to its speed and simplicity. However, as your project scales, storing large or numerous job payloads directly in Redis can lead to inefficiencies and memory constraints. A common solution is to offload job payloads to external databases while using Redis to manage queue metadata. In this blog, we’ll explore this approach and its benefits for small to medium-sized projects.",[1017,4372,4374],{"id":4373},"the-challenge-with-storing-payloads-in-redis","The Challenge with Storing Payloads in Redis",[88,4376,4377],{},"Redis stores all data in memory, making it extremely fast but also expensive to scale. If you store large job payloads or retain completed jobs for extended periods, Redis memory usage can grow rapidly. This can:",[117,4379,4380,4383,4386],{},[110,4381,4382],{},"Exhaust available memory.",[110,4384,4385],{},"Increase costs due to the high price of RAM compared to disk storage.",[110,4387,4388],{},"Impact the performance of other Redis operations.",[92,4390,4392],{"id":4391},"general-overview-of-cost-implications","General Overview of Cost Implications",[88,4394,4395],{},"Using Redis for both metadata and job payloads can significantly drive up costs for the following reasons:",[117,4397,4398,4404,4410],{},[110,4399,4400,4403],{},[113,4401,4402],{},"High RAM Costs",": Redis relies entirely on RAM, which is much more expensive than traditional disk storage. As your job payload size grows, the cost of scaling Redis to accommodate these payloads increases exponentially.",[110,4405,4406,4409],{},[113,4407,4408],{},"Retention Overhead",": Retaining large volumes of data, such as completed jobs or logs, in Redis can lead to frequent memory upgrades, further inflating costs.",[110,4411,4412,4415],{},[113,4413,4414],{},"Limited Scalability",": Memory constraints in Redis can limit the number of jobs it can handle, forcing earlier and more frequent investment in scaling infrastructure.",[1017,4417,4419],{"id":4418},"the-hybrid-approach-redis-for-metadata-database-for-payloads","The Hybrid Approach: Redis for Metadata, Database for Payloads",[88,4421,4422],{},"To address these challenges, you can store minimal job metadata in Redis and move the actual payloads to an external database such as MongoDB, PostgreSQL, or even an object storage service like Amazon S3. Here’s how it works:",[92,4424,4426],{"id":4425},"redis-as-metadata-storage",[113,4427,4428],{},"Redis as Metadata Storage",[88,4430,4431],{},"In this model, Redis holds only the essential metadata required for job queue management, such as:",[117,4433,4434,4437,4452,4455],{},[110,4435,4436],{},"Job ID",[110,4438,4439,4440,1239,4443,1239,4446,1239,4449,400],{},"Status (e.g., ",[123,4441,4442],{},"waiting",[123,4444,4445],{},"active",[123,4447,4448],{},"completed",[123,4450,4451],{},"failed",[110,4453,4454],{},"Timestamps (e.g., when the job was created or processed)",[110,4456,4457],{},"A reference to the payload stored in the external database",[88,4459,4460],{},"Example:",[160,4462,4465],{"className":4463,"code":4464,"language":1500},[1498],"{\n  \"id\": \"job123\",\n  \"status\": \"waiting\",\n  \"created_at\": \"2025-01-09T10:00:00Z\",\n  \"payload_ref\": \"mongodb:\u002F\u002Fdb\u002Fjobs\u002Fjob123\"\n}\n",[123,4466,4464],{"__ignoreMap":165},[92,4468,4470],{"id":4469},"external-database-for-job-payloads",[113,4471,4472],{},"External Database for Job Payloads",[88,4474,4475],{},"The job payload—the actual data required for processing—is saved in an external database. This could be:",[117,4477,4478,4484,4490],{},[110,4479,4480,4483],{},[113,4481,4482],{},"MongoDB",": Ideal for JSON-like documents or unstructured data.",[110,4485,4486,4489],{},[113,4487,4488],{},"PostgreSQL",": Suitable for structured, relational data.",[110,4491,4492,4495],{},[113,4493,4494],{},"Amazon S3",": Best for large binary files or blobs.",[88,4497,4460],{},[160,4499,4502],{"className":4500,"code":4501,"language":1500},[1498],"{\n  \"_id\": \"job123\",\n  \"type\": \"email\",\n  \"payload\": {\n    \"to\": \"user@example.com\",\n    \"subject\": \"Welcome!\",\n    \"message\": \"Thank you for joining.\"\n  },\n  \"created_at\": \"2025-01-09T10:00:00Z\"\n}\n",[123,4503,4501],{"__ignoreMap":165},[1017,4505,4507],{"id":4506},"workflow-example","Workflow Example",[88,4509,4510],{},"Let’s look at how this approach works in a typical producer-worker setup.",[92,4512,4514],{"id":4513},"producer-workflow",[113,4515,4516],{},"Producer Workflow",[107,4518,4519,4522],{},[110,4520,4521],{},"Save the job payload in the external database.",[110,4523,4524],{},"Create a job in Redis, including metadata and a reference (e.g., a database ID or URL) to the payload.",[92,4526,4528],{"id":4527},"worker-workflow",[113,4529,4530],{},"Worker Workflow",[107,4532,4533,4536,4539,4542],{},[110,4534,4535],{},"Retrieve job metadata from Redis.",[110,4537,4538],{},"Use the reference to fetch the actual payload from the external database.",[110,4540,4541],{},"Process the job.",[110,4543,4544],{},"Update the job status in Redis and optionally in the database.",[1017,4546,4548],{"id":4547},"benefits-of-this-approach","Benefits of This Approach",[92,4550,4552],{"id":4551},"_1-optimized-redis-memory-usage",[113,4553,4554],{},"1. Optimized Redis Memory Usage",[88,4556,4557],{},"By storing only metadata in Redis, you significantly reduce its memory footprint, allowing Redis to handle more queues and jobs without frequent scaling.",[92,4559,4561],{"id":4560},"_2-persistent-payload-storage",[113,4562,4563],{},"2. Persistent Payload Storage",[88,4565,4566],{},"Unlike Redis, which is in-memory and can lose data without persistence enabled, external databases provide durable, long-term storage for job payloads.",[92,4568,4570],{"id":4569},"_3-cost-efficiency",[113,4571,4572],{},"3. Cost Efficiency",[88,4574,4575],{},"RAM is costly compared to disk storage. Moving payloads to a database reduces your reliance on expensive memory resources.",[92,4577,4579],{"id":4578},"_4-scalability",[113,4580,4581],{},"4. Scalability",[88,4583,4584],{},"This approach decouples job data from Redis, making it easier to scale both the queue system and the storage system independently.",[92,4586,4588],{"id":4587},"_5-flexibility",[113,4589,4590],{},"5. Flexibility",[88,4592,4593],{},"You can use databases tailored to your data’s characteristics, such as MongoDB for unstructured data or PostgreSQL for structured records.",[1017,4595,4597],{"id":4596},"implementation-example","Implementation Example",[88,4599,4600,4601,4310,4603,4605,4606,4609,4610,4613],{},"Here’s how you can implement this pattern using ",[123,4602,3704],{},[123,4604,4482],{}," with a ",[123,4607,4608],{},"Node.js"," application and the ",[123,4611,4612],{},"BullMQ"," library.",[92,4615,4617],{"id":4616},"producer-example",[113,4618,4619],{},"Producer Example",[160,4621,4624],{"className":4622,"code":4623,"language":1500},[1498],"const { Queue } = require('bullmq');\nconst { MongoClient } = require('mongodb');\n\n\u002F\u002F MongoDB setup\nconst mongoClient = new MongoClient('mongodb:\u002F\u002Flocalhost:27017');\nawait mongoClient.connect();\nconst db = mongoClient.db('jobsDB');\nconst jobsCollection = db.collection('jobs');\n\n\u002F\u002F Save payload to MongoDB\nconst payload = { to: 'user@example.com', subject: 'Welcome!', message: 'Hello!' };\nconst jobId = 'job123';\nawait jobsCollection.insertOne({ _id: jobId, payload });\n\n\u002F\u002F Add job metadata to Redis\nconst queue = new Queue('emailQueue');\nawait queue.add('emailJob', { jobId, payloadRef: `mongodb:\u002F\u002FjobsDB\u002Fjobs\u002F${jobId}` });\n",[123,4625,4623],{"__ignoreMap":165},[92,4627,4629],{"id":4628},"worker-example",[113,4630,4631],{},"Worker Example",[160,4633,4636],{"className":4634,"code":4635,"language":1500},[1498],"const { Worker } = require('bullmq');\nconst { MongoClient } = require('mongodb');\n\n\u002F\u002F MongoDB setup\nconst mongoClient = new MongoClient('mongodb:\u002F\u002Flocalhost:27017');\nawait mongoClient.connect();\nconst db = mongoClient.db('jobsDB');\nconst jobsCollection = db.collection('jobs');\n\n\u002F\u002F Create worker to process jobs\nconst worker = new Worker('emailQueue', async (job) => {\n  \u002F\u002F Fetch payload from MongoDB\n  const jobData = await jobsCollection.findOne({ _id: job.data.jobId });\n\n  if (jobData) {\n    console.log('Processing job:', jobData.payload);\n    \u002F\u002F Process the job\n    \u002F\u002F ...\n  } else {\n    console.error('Job payload not found!');\n  }\n});\n",[123,4637,4635],{"__ignoreMap":165},[1017,4639,4641],{"id":4640},"key-considerations","Key Considerations",[107,4643,4644,4657,4662,4668],{},[110,4645,4646,4649,4650,4310,4653,4656],{},[113,4647,4648],{},"Retention Policies",": Use BullMQ’s ",[123,4651,4652],{},"removeOnComplete",[123,4654,4655],{},"removeOnFail"," options to prevent unnecessary data buildup in Redis.",[110,4658,4659,4661],{},[113,4660,3528],{},": Regularly monitor memory usage and job statistics using tools like RedisInsight or BullMQ UI.",[110,4663,4664,4667],{},[113,4665,4666],{},"Scaling",": For larger projects, use Redis Cluster to scale horizontally and distribute metadata storage.",[110,4669,4670,4673],{},[113,4671,4672],{},"Performance Tuning",": Optimize database queries to ensure minimal latency when fetching payloads.",[1017,4675,1009],{"id":1008},[88,4677,4678],{},"Using Redis for metadata and an external database for payloads is a smart way to optimize performance and scalability for small to medium-sized projects. This hybrid approach keeps Redis lean, reduces costs, and provides durable storage for job data. By implementing this pattern, you can handle higher workloads while maintaining efficiency and reliability.",{"title":165,"searchDepth":206,"depth":206,"links":4680},[4681,4682,4683,4684,4685,4686,4687],{"id":4373,"depth":212,"text":4374},{"id":4418,"depth":212,"text":4419},{"id":4506,"depth":212,"text":4507},{"id":4547,"depth":212,"text":4548},{"id":4596,"depth":212,"text":4597},{"id":4640,"depth":212,"text":4641},{"id":1008,"depth":212,"text":1009},"2025-01-09","Use external databases for payloads in Redis queues to reduce memory use, lower costs, and improve scalability for small to medium projects","https:\u002F\u002Fcdn.hashnode.com\u002Fres\u002Fhashnode\u002Fimage\u002Fupload\u002Fv1736397487442\u002F2e74c420-628c-4342-9c3f-f496c64ca18a.jpeg",{},{"title":30,"description":4689},"xvsj_XKHE-G-cu2Lhy_oHae2UPfcV6-I4s5yfNyy7YU",{"id":4695,"title":18,"author":4696,"body":4698,"date":4940,"description":4941,"extension":1069,"image":4942,"meta":4943,"minRead":206,"navigation":73,"path":19,"seo":4944,"stem":20,"__hash__":4945},"blog\u002Fblog\u002Fcomprehensive-guide-encrypting-images-uploading-to-cloudinary-and-serving-them-securely.md",{"name":80,"to":67,"username":81,"avatar":4697},{"src":83,"alt":80},{"type":85,"value":4699,"toc":4918},[4700,4709,4711,4717,4726,4732,4738,4741,4743,4749,4760,4766,4772,4778,4784,4790,4792,4798,4809,4815,4821,4824,4826,4832,4838,4844,4850,4856,4862,4868,4871,4873,4879,4885,4891,4894,4900,4906,4909,4911,4915],[88,4701,4702,4703,4708],{},"This guide explains how to implement a secure system for encrypting images, uploading them to a cloud storage solution (",[1026,4704,4707],{"href":4705,"rel":4706},"https:\u002F\u002Fcloudinary.com\u002F",[1030],"Cloudinary","), and serving them when needed. We’ll walk through each step in detail, from setting up key generation to encrypting and decrypting images, uploading to Cloudinary, and securely serving them. By the end, you'll understand how everything ties together to form a robust, secure image handling process.",[1014,4710],{},[2573,4712,4714],{"id":4713},"step-1-key-generation",[113,4715,4716],{},"Step 1: Key Generation",[88,4718,4719,4720,4725],{},"To secure the encryption and decryption process, we'll use ",[1026,4721,4724],{"href":4722,"rel":4723},"https:\u002F\u002Fwww.geeksforgeeks.org\u002Frsa-algorithm-cryptography\u002F",[1030],"RSA"," (asymmetric cryptography) to encrypt the AES (symmetric) key used for image encryption.",[1017,4727,4729],{"id":4728},"generating-rsa-keys",[113,4730,4731],{},"Generating RSA Keys",[160,4733,4736],{"className":4734,"code":4735,"language":1500},[1498],"import crypto from \"crypto\";\nimport fs from \"fs\";\n\n\u002F\u002F Generate RSA key pair\nconst { publicKey, privateKey } = crypto.generateKeyPairSync(\"rsa\", {\n  modulusLength: 2048, \u002F\u002F Key size in bits\n  publicKeyEncoding: {\n    type: \"spki\", \u002F\u002F Recommended format for public key\n    format: \"pem\",\n  },\n  privateKeyEncoding: {\n    type: \"pkcs8\", \u002F\u002F Recommended format for private key\n    format: \"pem\",\n  },\n});\n\n\u002F\u002F Save public key to a file\nfs.writeFileSync(\"public_key.pem\", publicKey);\nconsole.log(\"Public key saved to public_key.pem\");\n\n\u002F\u002F Save private key to a file\nfs.writeFileSync(\"private_key.pem\", privateKey);\nconsole.log(\"Private key saved to private_key.pem\");\n",[123,4737,4735],{"__ignoreMap":165},[88,4739,4740],{},"This generates an RSA key pair and saves the keys to files for use in encryption and decryption.",[1014,4742],{},[2573,4744,4746],{"id":4745},"step-2-setting-up-image-uploads",[113,4747,4748],{},"Step 2: Setting Up Image Uploads",[88,4750,4751,4752,4759],{},"We’ll use ",[1026,4753,4756],{"href":4754,"rel":4755},"https:\u002F\u002Fgithub.com\u002Fexpressjs\u002Fmulter#readme",[1030],[123,4757,4758],{},"multer",", a middleware for handling file uploads in Node.js, to handle user-uploaded images.",[1017,4761,4763],{"id":4762},"installing-multer",[113,4764,4765],{},"Installing Multer",[160,4767,4770],{"className":4768,"code":4769,"language":1500},[1498],"npm install multer\n",[123,4771,4769],{"__ignoreMap":165},[1017,4773,4775],{"id":4774},"configuring-multer",[113,4776,4777],{},"Configuring Multer",[160,4779,4782],{"className":4780,"code":4781,"language":1500},[1498],"import multer from \"multer\";\nimport path from \"path\";\n\nconst storage = multer.diskStorage({\n  destination: (req, file, cb) => {\n    cb(null, \"uploads\u002F\"); \u002F\u002F Directory to save uploaded files\n  },\n  filename: (req, file, cb) => {\n    const uniqueSuffix = Date.now() + \"-\" + Math.round(Math.random() * 1E9);\n    cb(null, `${uniqueSuffix}-${file.originalname}`);\n  },\n});\n\nexport const upload = multer({ storage });\n",[123,4783,4781],{"__ignoreMap":165},[88,4785,4786,4787,367],{},"This code configures Multer to save uploaded images to a directory called ",[123,4788,4789],{},"uploads\u002F",[1014,4791],{},[2573,4793,4795],{"id":4794},"step-3-encrypting-images",[113,4796,4797],{},"Step 3: Encrypting Images",[88,4799,4800,4801,4808],{},"We'll use ",[1026,4802,4805],{"href":4803,"rel":4804},"https:\u002F\u002Fwww.sharesecure.link\u002Farticles\u002Fthe-ultimate-developers-guide-to-aes-gcm-encryption-with-web-cryptography-api#understanding-encryption-how-it-all-works",[1030],[113,4806,4807],{},"AES-GCM"," (a symmetric encryption algorithm) to encrypt the image data and RSA to securely store the encryption key.",[1017,4810,4812],{"id":4811},"encryption-function",[113,4813,4814],{},"Encryption Function",[160,4816,4819],{"className":4817,"code":4818,"language":1500},[1498],"import crypto from \"crypto\";\nimport fs from \"fs\";\n\n\u002F\u002F Load RSA public key\nconst publicKey = fs.readFileSync(\"public_key.pem\", \"utf8\");\n\nexport const encryptImage = (imageFile) => {\n  const algorithm = \"aes-256-gcm\";\n  const key = crypto.randomBytes(32); \u002F\u002F AES key\n  const iv = crypto.randomBytes(16); \u002F\u002F Initialization vector\n  const cipher = crypto.createCipheriv(algorithm, key, iv);\n\n  \u002F\u002F Read the image file into a buffer\n  const imageBuffer = fs.readFileSync(imageFile.path);\n\n  let encrypted = cipher.update(imageBuffer);\n  encrypted = Buffer.concat([encrypted, cipher.final()]);\n  const authTag = cipher.getAuthTag();\n\n  \u002F\u002F Encrypt the AES key with RSA public key\n  const encryptedKey = crypto.publicEncrypt(publicKey, key);\n\n  return {\n    iv: iv.toString(\"hex\"),\n    encryptedData: encrypted.toString(\"base64\"),\n    authTag: authTag.toString(\"hex\"),\n    encryptedKey: encryptedKey.toString(\"base64\"),\n  };\n};\n",[123,4820,4818],{"__ignoreMap":165},[88,4822,4823],{},"This function encrypts the image file using AES-GCM and encrypts the AES key with the RSA public key.",[1014,4825],{},[2573,4827,4829],{"id":4828},"step-4-uploading-to-cloudinary",[113,4830,4831],{},"Step 4: Uploading to Cloudinary",[1017,4833,4835],{"id":4834},"installing-cloudinary-sdk",[113,4836,4837],{},"Installing Cloudinary SDK",[160,4839,4842],{"className":4840,"code":4841,"language":1500},[1498],"npm install cloudinary\n",[123,4843,4841],{"__ignoreMap":165},[1017,4845,4847],{"id":4846},"configuring-cloudinary",[113,4848,4849],{},"Configuring Cloudinary",[160,4851,4854],{"className":4852,"code":4853,"language":1500},[1498],"import { v2 as cloudinary } from \"cloudinary\";\n\ncloudinary.config({\n  cloud_name: \"your-cloud-name\", \u002F\u002F Replace with your Cloudinary cloud name\n  api_key: \"your-api-key\",       \u002F\u002F Replace with your Cloudinary API key\n  api_secret: \"your-api-secret\", \u002F\u002F Replace with your Cloudinary API secret\n});\n",[123,4855,4853],{"__ignoreMap":165},[1017,4857,4859],{"id":4858},"uploading-encrypted-images",[113,4860,4861],{},"Uploading Encrypted Images",[160,4863,4866],{"className":4864,"code":4865,"language":1500},[1498],"import fs from \"fs\";\n\nexport const uploadToCloudinary = async (encryptedData, fileName) => {\n  const tempFilePath = `temp\u002F${fileName}.json`;\n  fs.writeFileSync(tempFilePath, JSON.stringify(encryptedData));\n\n  const result = await cloudinary.uploader.upload(tempFilePath, {\n    resource_type: \"raw\",\n    folder: \"encrypted_images\",\n  });\n\n  fs.unlinkSync(tempFilePath); \u002F\u002F Delete temporary file\n  return result.secure_url; \u002F\u002F Return the uploaded file URL\n};\n",[123,4867,4865],{"__ignoreMap":165},[88,4869,4870],{},"This uploads the encrypted image data as a JSON file to Cloudinary.",[1014,4872],{},[2573,4874,4876],{"id":4875},"step-5-decrypting-and-serving-images",[113,4877,4878],{},"Step 5: Decrypting and Serving Images",[1017,4880,4882],{"id":4881},"decryption-function",[113,4883,4884],{},"Decryption Function",[160,4886,4889],{"className":4887,"code":4888,"language":1500},[1498],"\u002F\u002F Load RSA private key\nconst privateKey = fs.readFileSync(\"private_key.pem\", \"utf8\");\n\nexport const decryptImage = (encryptedData) => {\n  const { encryptedData: data, encryptedKey, iv, authTag } = encryptedData;\n\n  \u002F\u002F Decrypt the AES key using RSA private key\n  const key = crypto.privateDecrypt(privateKey, Buffer.from(encryptedKey, \"base64\"));\n\n  const decipher = crypto.createDecipheriv(\"aes-256-gcm\", key, Buffer.from(iv, \"hex\"));\n  decipher.setAuthTag(Buffer.from(authTag, \"hex\"));\n\n  let decrypted = decipher.update(Buffer.from(data, \"base64\"));\n  decrypted = Buffer.concat([decrypted, decipher.final()]);\n\n  return decrypted;\n};\n",[123,4890,4888],{"__ignoreMap":165},[88,4892,4893],{},"This function decrypts the AES-encrypted image using the RSA private key and the provided metadata.",[1017,4895,4897],{"id":4896},"serving-decrypted-images",[113,4898,4899],{},"Serving Decrypted Images",[160,4901,4904],{"className":4902,"code":4903,"language":1500},[1498],"import axios from \"axios\";\n\nexport const decodeUserImageController = async (req, res) => {\n  const { version, public_id } = req.params;\n  if (!public_id) {\n    res.status(400).send(\"Invalid request: public_id is required\");\n    return;\n  }\n\n  \u002F\u002F Fetch encrypted data from Cloudinary\n  const encryptedData = (await axios.get(`https:\u002F\u002Fres.cloudinary.com\u002Fyour-cloud-name\u002Fraw\u002Fupload\u002Fv${version}\u002F${public_id}.json`)).data;\n\n  \u002F\u002F Decrypt the image\n  const decrypted = decryptImage(encryptedData);\n\n  \u002F\u002F Serve the decrypted image\n  res.type(\"image\u002Fpng\").send(decrypted);\n};\n",[123,4905,4903],{"__ignoreMap":165},[88,4907,4908],{},"This code fetches the encrypted image metadata from Cloudinary, decrypts it, and serves it as a response.",[1014,4910],{},[2573,4912,4913],{"id":1008},[113,4914,1009],{},[88,4916,4917],{},"By following this guide, you can implement a secure system for encrypting and storing sensitive images. Using a combination of AES-GCM for encryption and RSA for key security ensures strong protection. Cloudinary provides a scalable storage solution for encrypted data, and the ability to decrypt and serve images on demand makes this system both flexible and secure.",{"title":165,"searchDepth":206,"depth":206,"links":4919},[4920,4923,4927,4930,4935,4939],{"id":4713,"depth":206,"text":4716,"children":4921},[4922],{"id":4728,"depth":212,"text":4731},{"id":4745,"depth":206,"text":4748,"children":4924},[4925,4926],{"id":4762,"depth":212,"text":4765},{"id":4774,"depth":212,"text":4777},{"id":4794,"depth":206,"text":4797,"children":4928},[4929],{"id":4811,"depth":212,"text":4814},{"id":4828,"depth":206,"text":4831,"children":4931},[4932,4933,4934],{"id":4834,"depth":212,"text":4837},{"id":4846,"depth":212,"text":4849},{"id":4858,"depth":212,"text":4861},{"id":4875,"depth":206,"text":4878,"children":4936},[4937,4938],{"id":4881,"depth":212,"text":4884},{"id":4896,"depth":212,"text":4899},{"id":1008,"depth":206,"text":1009},"2025-01-08","Learn to encrypt images, upload securely to Cloudinary, and serve them safely with this comprehensive guide on secure image handling processes","https:\u002F\u002Fcdn.hashnode.com\u002Fres\u002Fhashnode\u002Fimage\u002Fupload\u002Fv1736336845591\u002F69561304-6153-4593-af17-8b70ed9e0c23.png",{},{"title":18,"description":4941},"xZ--h-lzziUlVwdCHYYvIQ0TDDm-H-xEIskD8MJqHJg",{"id":4947,"title":10,"author":4948,"body":4950,"date":5149,"description":5150,"extension":1069,"image":5151,"meta":5152,"minRead":212,"navigation":73,"path":11,"seo":5153,"stem":12,"__hash__":5154},"blog\u002Fblog\u002Fa-guide-to-server-side-rendering-ssr-with-vite-and-reactjs.md",{"name":80,"to":67,"username":81,"avatar":4949},{"src":83,"alt":80},{"type":85,"value":4951,"toc":5142},[4952,4957,4959,4962,4965,4967,4971,4974,4977,4997,4999,5003,5006,5008,5012,5015,5019,5026,5032,5049,5056,5065,5071,5075,5089,5095,5099,5102,5108,5112,5119,5125,5127,5129,5132,5135,5137],[88,4953,4954],{},[113,4955,4956],{},"Title: A Guide to Server-Side Rendering (SSR) with Vite and React.js",[1014,4958],{},[1017,4960,4961],{"id":2575},"Introduction",[88,4963,4964],{},"Server-Side Rendering (SSR) has become an essential technique for enhancing the performance and user experience of modern web applications. By pre-rendering pages on the server, SSR can drastically improve page load speeds and make websites more SEO-friendly. In this guide, we’ll explore how to implement SSR with React.js and Vite, step by step.",[1014,4966],{},[1017,4968,4970],{"id":4969},"what-is-server-side-rendering-ssr","What is Server-Side Rendering (SSR)?",[88,4972,4973],{},"In traditional client-side rendering (CSR), when a user visits a webpage, they receive a bare HTML page which then loads JavaScript and other resources, like CSS files. Only after these resources are loaded does the content become visible to the user. While this approach works, it can create delays in page load time, especially for users on slow connections.",[88,4975,4976],{},"SSR addresses this issue by rendering the HTML on the server, sending a fully rendered page to the client, and allowing the user to see the content immediately without waiting for JavaScript to load. This offers several benefits:",[117,4978,4979,4985,4991],{},[110,4980,4981,4984],{},[113,4982,4983],{},"Faster initial page load",": The user sees content more quickly because the server sends a complete HTML document.",[110,4986,4987,4990],{},[113,4988,4989],{},"Better SEO",": Since search engines can crawl the fully rendered HTML, the content is indexed more effectively.",[110,4992,4993,4996],{},[113,4994,4995],{},"Improved user experience",": The user can interact with content sooner, leading to higher engagement rates.",[1014,4998],{},[1017,5000,5002],{"id":5001},"performance-considerations-with-ssr","Performance Considerations with SSR",[88,5004,5005],{},"While SSR reduces the time to the Largest Contentful Paint (LCP), it can potentially increase the Interaction to Next Paint (INP), which is the time it takes for the user to interact with the page. A poorly implemented SSR setup might result in users seeing content but being unable to interact with it until the necessary JavaScript has loaded. It’s important to strike a balance between fast rendering and interactive elements to ensure smooth user interactions.",[1014,5007],{},[1017,5009,5011],{"id":5010},"steps-to-set-up-ssr-in-vite-and-reactjs","Steps to Set Up SSR in Vite and React.js",[88,5013,5014],{},"We’ll break down the implementation of SSR in a Vite and React.js project into a few manageable steps.",[92,5016,5018],{"id":5017},"_1-create-a-clientapp-component","1. Create a ClientApp Component",[88,5020,5021,5022,5025],{},"First, we need to create a ",[123,5023,5024],{},"ClientApp.jsx"," component to handle the client-side rendering logic.",[160,5027,5030],{"className":5028,"code":5029,"language":1500},[1498],"\u002F\u002F ClientApp.jsx\nimport { hydrateRoot } from 'react-dom\u002Fclient';\nimport { BrowserRouter } from 'react-router-dom';\nimport App from '.\u002FApp';\n\nhydrateRoot(document.getElementById('root'), \n  \u003CBrowserRouter>\n    \u003CApp \u002F>\n  BrowserRouter>\n);\n",[123,5031,5029],{"__ignoreMap":165},[88,5033,5034,5035,5038,5039,4310,5042,5038,5045,5048],{},"This code imports ",[123,5036,5037],{},"hydrateRoot"," from ",[123,5040,5041],{},"react-dom\u002Fclient",[123,5043,5044],{},"BrowserRouter",[123,5046,5047],{},"react-router-dom"," to ensure the React application is correctly rendered in the browser.",[92,5050,5052,5053],{"id":5051},"_2-modify-indexhtml","2. Modify ",[123,5054,5055],{},"index.html",[88,5057,5058,5059,5061,5062,5064],{},"Next, we need to update the ",[123,5060,5055],{}," file to load the ",[123,5063,5024],{}," file and add a parsing token to facilitate streaming.",[160,5066,5069],{"className":5067,"code":5068,"language":1500},[1498],"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>Vite + React + SSRtitle>\n  head>\n  \u003Cbody>\n    \u003Cdiv id=\"root\">div>\n    \u003Cscript type=\"module\" src=\".\u002Fsrc\u002FClientApp.jsx\">script>\n  body>\nhtml>\n",[123,5070,5068],{"__ignoreMap":165},[92,5072,5074],{"id":5073},"_3-create-a-serverapp-component","3. Create a ServerApp Component",[88,5076,5077,5078,5081,5082,5038,5085,5088],{},"Now, we’ll create a ",[123,5079,5080],{},"ServerApp.jsx"," file for the server-side rendering logic. It will use ",[123,5083,5084],{},"renderToPipeableStream",[123,5086,5087],{},"react-dom\u002Fserver"," to stream the HTML.",[160,5090,5093],{"className":5091,"code":5092,"language":1500},[1498],"\u002F\u002F ServerApp.jsx\nimport { renderToPipeableStream } from 'react-dom\u002Fserver';\nimport { StaticRouter } from 'react-router-dom\u002Fserver';\nimport App from '.\u002FApp';\n\nexport default function render(url, opts) {\n  const stream = renderToPipeableStream(\n    \u003CStaticRouter location={url}>\n      \u003CApp \u002F>\n    StaticRouter>,\n    opts\n  );\n\n  return stream;\n}\n",[123,5094,5092],{"__ignoreMap":165},[92,5096,5098],{"id":5097},"_4-update-build-scripts","4. Update Build Scripts",[88,5100,5101],{},"To build both the client and server bundles, we need to update the build scripts.",[160,5103,5106],{"className":5104,"code":5105,"language":1500},[1498],"{\n  \"scripts\": {\n    \"build:client\": \"vite build --outDir ..\u002Fdist\u002Fclient\",\n    \"build:server\": \"vite build --outDir ..\u002Fdist\u002Fserver --ssr ServerApp.jsx\",\n    \"build\": \"npm run build:client && npm run build:server\",\n    \"start\": \"node server.js\"\n  },\n  \"type\": \"module\"\n}\n",[123,5107,5105],{"__ignoreMap":165},[92,5109,5111],{"id":5110},"_5-set-up-the-nodejs-server","5. Set Up the Node.js Server",[88,5113,5114,5115,5118],{},"We need to set up an Express server in ",[123,5116,5117],{},"server.js"," to handle SSR and static assets.",[160,5120,5123],{"className":5121,"code":5122,"language":1500},[1498],"\u002F\u002F server.js\nimport express from 'express';\nimport fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport renderApp from '.\u002Fdist\u002Fserver\u002FServerApp.js';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst PORT = process.env.PORT || 3001;\n\nconst html = fs.readFileSync(path.resolve(__dirname, '.\u002Fdist\u002Fclient\u002Findex.html')).toString();\nconst [head, tail] = html.split('');\n\nconst app = express();\n\n\u002F\u002F Serve static assets\napp.use('\u002Fassets', express.static(path.resolve(__dirname, '.\u002Fdist\u002Fclient\u002Fassets')));\n\n\u002F\u002F Handle all other routes with SSR\napp.use((req, res) => {\n  res.write(head);\n\n  const stream = renderApp(req.url, {\n    onShellReady() {\n      stream.pipe(res);\n    },\n    onShellError(err) {\n      console.error(err);\n      res.status(500).send('Internal Server Error');\n    },\n    onAllReady() {\n      res.write(tail);\n      res.end();\n    },\n    onError(err) {\n      console.error(err);\n    }\n  });\n});\n\napp.listen(PORT, () => {\n  console.log(`Server is running at http:\u002F\u002Flocalhost:${PORT}`);\n});\n",[123,5124,5122],{"__ignoreMap":165},[1014,5126],{},[1017,5128,1009],{"id":1008},[88,5130,5131],{},"By following these steps, we can implement server-side rendering (SSR) with React.js and Vite to improve the performance of our web application. The key benefit of SSR is that it reduces load times by delivering a fully rendered HTML page to the user. By streamlining content delivery, we create a faster, more responsive user experience.",[88,5133,5134],{},"This guide covered setting up SSR with React.js using Vite, creating necessary components, building server and client bundles, and configuring an Express server to handle SSR. With these optimizations in place, your application will be ready for better performance, enhanced SEO, and improved user engagement.",[1014,5136],{},[88,5138,5139],{},[2960,5140,5141],{},"Ready to optimize your React app with SSR? Follow these steps and watch your app's performance soar!",{"title":165,"searchDepth":206,"depth":206,"links":5143},[5144,5145,5146,5147,5148],{"id":2575,"depth":212,"text":4961},{"id":4969,"depth":212,"text":4970},{"id":5001,"depth":212,"text":5002},{"id":5010,"depth":212,"text":5011},{"id":1008,"depth":212,"text":1009},"2025-01-07","Learn to implement Server-Side Rendering (SSR) with React.js and Vite for improved web performance and SEO","https:\u002F\u002Fcdn.hashnode.com\u002Fres\u002Fhashnode\u002Fimage\u002Fupload\u002Fv1736255453305\u002F5d8c01c4-0478-4570-a0d2-8a343c1c17b4.png",{},{"title":10,"description":5150},"wmeFga7yYi5CzVsu08SA1JwvarTSRUsWMuFsLgjhZgc",{"id":5156,"title":42,"author":5157,"body":5159,"date":5709,"description":5710,"extension":1069,"image":5711,"meta":5712,"minRead":233,"navigation":73,"path":43,"seo":5713,"stem":44,"__hash__":5714},"blog\u002Fblog\u002Fhow-queues-enhance-your-application-development-process.md",{"name":80,"to":67,"username":81,"avatar":5158},{"src":2569,"alt":80},{"type":85,"value":5160,"toc":5686},[5161,5164,5166,5170,5176,5179,5185,5190,5204,5209,5217,5219,5225,5228,5234,5239,5253,5258,5266,5268,5274,5277,5283,5288,5302,5307,5315,5317,5324,5327,5333,5337,5351,5356,5361,5363,5370,5373,5379,5383,5397,5402,5407,5409,5416,5419,5425,5430,5444,5449,5457,5459,5463,5466,5470,5490,5494,5500,5504,5512,5518,5522,5542,5544,5548,5561,5587,5589,5593,5596,5679,5681,5683],[88,5162,5163],{},"When building modern web applications, developers often encounter scenarios that involve sending emails, processing uploads, or performing other time-consuming tasks. Managing these operations effectively is crucial for delivering a seamless user experience. One powerful tool that can help is a queue. This article explores why queues are essential, how they work, and the challenges you might face without them.",[1014,5165],{},[2573,5167,5169],{"id":5168},"the-problems-of-not-using-a-queue","The Problems of Not Using a Queue",[1017,5171,3547,5173],{"id":5172},"_1-performance-impact",[113,5174,5175],{},"Performance Impact",[88,5177,5178],{},"When your application directly handles time-intensive tasks like sending emails, users can experience significant delays. For example:",[160,5180,5183],{"className":5181,"code":5182,"language":1500},[1498],"\u002F\u002F Without Queue: 100 simultaneous signups\n\u002F\u002F Each waits for email to send\n\u002F\u002F If each email takes 1 second:\n\u002F\u002F Last user waits 100 seconds\n\u002F\u002F If any email fails, that user's signup fails\n\nasync signUpController(req, res) {\n  \u002F\u002F User creation logic...\n  await mailService.sendEmailVerification(email, verificationLink); \u002F\u002F Blocks for 1-2 seconds\n  \u002F\u002F Send response to user...\n}\n",[123,5184,5182],{"__ignoreMap":165},[88,5186,5187],{},[113,5188,5189],{},"Why is this bad?",[117,5191,5192,5198],{},[110,5193,5194,5197],{},[113,5195,5196],{},"Slow responses:"," Users have to wait while the server processes tasks, resulting in a poor experience.",[110,5199,5200,5203],{},[113,5201,5202],{},"Scalability issues:"," If traffic increases, the system becomes slower and less responsive.",[88,5205,5206],{},[113,5207,5208],{},"What happens when a queue is used?",[117,5210,5211,5214],{},[110,5212,5213],{},"Tasks like sending emails are pushed to a queue and processed in the background.",[110,5215,5216],{},"Users get instant feedback without waiting for the task to complete.",[1014,5218],{},[1017,5220,3574,5222],{"id":5221},"_2-failed-emails",[113,5223,5224],{},"Failed Emails",[88,5226,5227],{},"Without retries, failed email sends can cause major problems:",[160,5229,5232],{"className":5230,"code":5231,"language":1500},[1498],"try {\n  await mailService.sendEmailVerification(email, verificationLink);\n} catch (error) {\n  console.error('Email failed:', error);\n}\n",[123,5233,5231],{"__ignoreMap":165},[88,5235,5236],{},[113,5237,5238],{},"Problems:",[117,5240,5241,5247],{},[110,5242,5243,5246],{},[113,5244,5245],{},"Lost emails:"," Users never receive critical messages like verification links.",[110,5248,5249,5252],{},[113,5250,5251],{},"Manual resending required:"," Users must contact support or try again, adding friction.",[88,5254,5255],{},[113,5256,5257],{},"How does a queue solve this?",[117,5259,5260,5263],{},[110,5261,5262],{},"Tasks that fail can be retried automatically based on predefined rules.",[110,5264,5265],{},"This ensures that temporary issues (e.g., network problems) don’t result in permanent failures.",[1014,5267],{},[1017,5269,3904,5271],{"id":5270},"_3-rate-limiting-issues",[113,5272,5273],{},"Rate Limiting Issues",[88,5275,5276],{},"Most email providers limit how many emails you can send in a given time frame. If you send too many at once:",[160,5278,5281],{"className":5279,"code":5280,"language":1500},[1498],"for (const user of users) {\n  await mailService.sendEmailVerification(user.email, link);\n}\n",[123,5282,5280],{"__ignoreMap":165},[88,5284,5285],{},[113,5286,5287],{},"Consequences:",[117,5289,5290,5296],{},[110,5291,5292,5295],{},[113,5293,5294],{},"Rate limit exceeded:"," Your email service might block further requests.",[110,5297,5298,5301],{},[113,5299,5300],{},"Delayed or failed deliveries:"," Users may not receive their emails promptly.",[88,5303,5304],{},[113,5305,5306],{},"Queue solution:",[117,5308,5309,5312],{},[110,5310,5311],{},"The queue can process tasks at a controlled rate, respecting provider limits.",[110,5313,5314],{},"This prevents exceeding limits and ensures smooth delivery.",[1014,5316],{},[1017,5318,5320,5321],{"id":5319},"_4-resource-management","4. ",[113,5322,5323],{},"Resource Management",[88,5325,5326],{},"Handling multiple requests simultaneously without a queue consumes significant server resources:",[160,5328,5331],{"className":5329,"code":5330,"language":1500},[1498],"app.post('\u002Fsignup', async (req, res) => {\n  await mailService.sendEmailVerification(email, link);\n});\n",[123,5332,5330],{"__ignoreMap":165},[88,5334,5335],{},[113,5336,5238],{},[117,5338,5339,5345],{},[110,5340,5341,5344],{},[113,5342,5343],{},"High resource usage:"," Server connections remain open until tasks complete.",[110,5346,5347,5350],{},[113,5348,5349],{},"Limited scalability:"," The system can’t handle a surge in requests.",[88,5352,5353],{},[113,5354,5355],{},"How queues help:",[117,5357,5358],{},[110,5359,5360],{},"By offloading tasks to the queue, server resources are freed up for handling more requests.",[1014,5362],{},[1017,5364,5366,5367],{"id":5365},"_5-no-prioritization","5. ",[113,5368,5369],{},"No Prioritization",[88,5371,5372],{},"Critical tasks like password resets might compete with less urgent ones:",[160,5374,5377],{"className":5375,"code":5376,"language":1500},[1498],"await mailService.sendPasswordReset(email, link);\nawait mailService.sendNewsletter(subscribers);\n",[123,5378,5376],{"__ignoreMap":165},[88,5380,5381],{},[113,5382,5238],{},[117,5384,5385,5391],{},[110,5386,5387,5390],{},[113,5388,5389],{},"Delayed critical tasks:"," Users waiting for password resets might experience delays.",[110,5392,5393,5396],{},[113,5394,5395],{},"Inefficient handling:"," Non-urgent tasks consume resources meant for high-priority ones.",[88,5398,5399],{},[113,5400,5401],{},"Queues enable prioritization:",[117,5403,5404],{},[110,5405,5406],{},"You can configure the queue to process critical tasks first.",[1014,5408],{},[1017,5410,5412,5413],{"id":5411},"_6-monitoring-difficulties","6. ",[113,5414,5415],{},"Monitoring Difficulties",[88,5417,5418],{},"Without a centralized system, tracking task failures can be challenging:",[160,5420,5423],{"className":5421,"code":5422,"language":1500},[1498],"try {\n  await mailService.sendEmailVerification(email, link);\n} catch (error) {\n  console.error(error);\n}\n",[123,5424,5422],{"__ignoreMap":165},[88,5426,5427],{},[113,5428,5429],{},"Issues:",[117,5431,5432,5438],{},[110,5433,5434,5437],{},[113,5435,5436],{},"No visibility:"," Failures are scattered across logs.",[110,5439,5440,5443],{},[113,5441,5442],{},"Hard to debug:"," Analyzing trends or patterns in failures becomes difficult.",[88,5445,5446],{},[113,5447,5448],{},"With a queue:",[117,5450,5451,5454],{},[110,5452,5453],{},"A dashboard provides real-time monitoring of task status.",[110,5455,5456],{},"You can analyze failures and optimize performance.",[1014,5458],{},[2573,5460,5462],{"id":5461},"the-benefits-of-using-a-queue","The Benefits of Using a Queue",[88,5464,5465],{},"Queues are like a buffer for your tasks, ensuring efficient and reliable processing. Let’s revisit the earlier scenario:",[1017,5467,5469],{"id":5468},"without-a-queue","Without a Queue:",[117,5471,5472,5478,5484],{},[110,5473,5474,5477],{},[113,5475,5476],{},"100 simultaneous signups:"," Each waits for email sending to complete.",[110,5479,5480,5483],{},[113,5481,5482],{},"If each email takes 1 second:"," The last user waits 100 seconds.",[110,5485,5486,5489],{},[113,5487,5488],{},"If an email fails:"," The corresponding user’s signup process fails.",[1017,5491,5493],{"id":5492},"with-a-queue","With a Queue:",[160,5495,5498],{"className":5496,"code":5497,"language":1500},[1498],"\u002F\u002F With Queue: 100 simultaneous signups\nsignUpController(req, res) {\n  \u002F\u002F Queue job and return immediately\n  await queueEmailService.queueVerificationEmail(email, link);\n  \u002F\u002F Response sent in milliseconds\n  \u002F\u002F Emails processed in background\n  \u002F\u002F Failed emails automatically retried\n  \u002F\u002F Rate limiting handled automatically\n}\n",[123,5499,5497],{"__ignoreMap":165},[88,5501,5502],{},[113,5503,3155],{},[107,5505,5506,5509],{},[110,5507,5508],{},"The API pushes the email task to the queue and responds immediately.",[110,5510,5511],{},"A worker processes queued tasks in the background:",[160,5513,5516],{"className":5514,"code":5515,"language":1500},[1498],"emailQueue.process(async (job) => {\n  const { email, link } = job.data;\n  await mailService.sendEmailVerification(email, link);\n});\n",[123,5517,5515],{"__ignoreMap":165},[88,5519,5520],{},[113,5521,3926],{},[117,5523,5524,5530,5536],{},[110,5525,5526,5529],{},[113,5527,5528],{},"Fast responses:"," Users aren’t kept waiting.",[110,5531,5532,5535],{},[113,5533,5534],{},"Retry logic:"," Temporary failures are automatically retried.",[110,5537,5538,5541],{},[113,5539,5540],{},"Rate limit management:"," Tasks are processed at a controlled pace.",[1014,5543],{},[2573,5545,5547],{"id":5546},"how-queues-work","How Queues Work",[88,5549,5550,5551,1239,5554,5556,5557,5560],{},"A queue system like ",[113,5552,5553],{},"RabbitMQ",[113,5555,3704],{},", or ",[113,5558,5559],{},"AWS SQS"," can:",[117,5562,5563,5569,5575,5581],{},[110,5564,5565,5568],{},[113,5566,5567],{},"Decouple tasks:"," Perform time-consuming operations asynchronously.",[110,5570,5571,5574],{},[113,5572,5573],{},"Prioritize tasks:"," Handle critical operations first.",[110,5576,5577,5580],{},[113,5578,5579],{},"Enable retries:"," Automatically retry failed tasks.",[110,5582,5583,5586],{},[113,5584,5585],{},"Provide monitoring:"," Track task status and analyze failures through dashboards.",[1014,5588],{},[2573,5590,5592],{"id":5591},"recommended-queue-services","Recommended Queue Services",[88,5594,5595],{},"Here are some popular queue services and libraries to consider:",[107,5597,5598,5614,5627,5640,5653,5666],{},[110,5599,5600,5603],{},[113,5601,5602],{},"BullMQ:",[117,5604,5605,5608,5611],{},[110,5606,5607],{},"Built on Redis.",[110,5609,5610],{},"Ideal for JavaScript\u002FTypeScript applications.",[110,5612,5613],{},"Provides job scheduling, retries, and a user-friendly UI for monitoring.",[110,5615,5616,5619],{},[113,5617,5618],{},"Bee-Queue:",[117,5620,5621,5624],{},[110,5622,5623],{},"Lightweight and fast.",[110,5625,5626],{},"Focused on simplicity and performance for Node.js applications.",[110,5628,5629,5632],{},[113,5630,5631],{},"RabbitMQ:",[117,5633,5634,5637],{},[110,5635,5636],{},"Enterprise-grade message broker.",[110,5638,5639],{},"Supports multiple protocols and complex routing.",[110,5641,5642,5645],{},[113,5643,5644],{},"AWS SQS (Simple Queue Service):",[117,5646,5647,5650],{},[110,5648,5649],{},"Fully managed by AWS.",[110,5651,5652],{},"Highly scalable with seamless integration into AWS ecosystem.",[110,5654,5655,5658],{},[113,5656,5657],{},"Redis Streams:",[117,5659,5660,5663],{},[110,5661,5662],{},"Stream-based messaging for real-time applications.",[110,5664,5665],{},"Suitable for lightweight and high-speed use cases.",[110,5667,5668,5671],{},[113,5669,5670],{},"Kafka:",[117,5672,5673,5676],{},[110,5674,5675],{},"Distributed streaming platform.",[110,5677,5678],{},"Excellent for event-driven architectures and large-scale data processing.",[1014,5680],{},[2573,5682,1009],{"id":1008},[88,5684,5685],{},"Using a queue is like having a post office for your application. Instead of personally delivering each message, the queue manages task distribution, retries, and prioritization. This approach enhances performance, reliability, and scalability. Whether you're sending emails, processing uploads, or handling other long-running tasks, integrating a queue system is a must-have for modern applications.",{"title":165,"searchDepth":206,"depth":206,"links":5687},[5688,5702,5706,5707,5708],{"id":5168,"depth":206,"text":5169,"children":5689},[5690,5692,5694,5696,5698,5700],{"id":5172,"depth":212,"text":5691},"1. Performance Impact",{"id":5221,"depth":212,"text":5693},"2. Failed Emails",{"id":5270,"depth":212,"text":5695},"3. Rate Limiting Issues",{"id":5319,"depth":212,"text":5697},"4. Resource Management",{"id":5365,"depth":212,"text":5699},"5. No Prioritization",{"id":5411,"depth":212,"text":5701},"6. Monitoring Difficulties",{"id":5461,"depth":206,"text":5462,"children":5703},[5704,5705],{"id":5468,"depth":212,"text":5469},{"id":5492,"depth":212,"text":5493},{"id":5546,"depth":206,"text":5547},{"id":5591,"depth":206,"text":5592},{"id":1008,"depth":206,"text":1009},"2025-01-04","When building modern web applications, developers often encounter scenarios that involve sending emails, processing uploads, or performing other time-consuming tasks. Learn how queues keep your app responsive and reliable.","\u002Fimages\u002Fblog\u002Fhow-queues-enhance-your-application-development-process.png",{},{"title":42,"description":5710},"WqdaAfecyoVaAmm2_pPCV-uuWG6ohwwlTGlL_r5I-ec",{"id":5716,"title":54,"author":5717,"body":5719,"date":5963,"description":5964,"extension":1069,"image":5965,"meta":5966,"minRead":212,"navigation":73,"path":55,"seo":5967,"stem":56,"__hash__":5968},"blog\u002Fblog\u002Fusing-multer-for-local-file-uploads-benefits-trade-offs-and-cloud-transfer-integration.md",{"name":80,"to":67,"username":81,"avatar":5718},{"src":83,"alt":80},{"type":85,"value":5720,"toc":5940},[5721,5728,5732,5736,5750,5754,5770,5774,5786,5790,5813,5817,5829,5833,5845,5849,5852,5856,5861,5865,5870,5874,5879,5883,5888,5892,5897,5901,5905,5913,5917,5922,5926,5931,5935],[88,5722,5723,5724,5727],{},"Using ",[113,5725,5726],{},"Multer"," to upload files locally before transferring them to a cloud storage service like AWS S3 has some distinct advantages and trade-offs compared to directly uploading to the cloud. Here’s a breakdown of why you might choose this approach and the benefits it offers:",[2573,5729,5731],{"id":5730},"advantages-of-using-multer-for-local-uploads-before-cloud-transfer","Advantages of Using Multer for Local Uploads Before Cloud Transfer",[1017,5733,5735],{"id":5734},"_1-easier-development-and-debugging","1. Easier Development and Debugging",[117,5737,5738,5744],{},[110,5739,5740,5743],{},[113,5741,5742],{},"Why",": During development, you may not want to configure cloud services or deal with network latency. Multer allows you to save files locally, making it easier to debug file upload logic.",[110,5745,5746,5749],{},[113,5747,5748],{},"Use Case",": Testing file validation, file structure, or debugging without relying on cloud APIs.",[1017,5751,5753],{"id":5752},"_2-custom-processing-before-upload","2. Custom Processing Before Upload",[117,5755,5756,5761],{},[110,5757,5758,5760],{},[113,5759,5742],{},": When files are uploaded locally first, you can process or manipulate them (e.g., resizing images, compressing files, or sanitizing metadata) before uploading them to the cloud.",[110,5762,5763,5765,5766,5769],{},[113,5764,5748],{},": Optimizing images with libraries like ",[123,5767,5768],{},"sharp"," or extracting metadata.",[1017,5771,5773],{"id":5772},"_3-lower-initial-latency","3. Lower Initial Latency",[117,5775,5776,5781],{},[110,5777,5778,5780],{},[113,5779,5742],{},": Uploading to local storage is generally faster than directly uploading to a cloud service, especially for large files or when working in a local development environment.",[110,5782,5783,5785],{},[113,5784,5748],{},": High-latency internet connections or environments where users expect immediate acknowledgment of their upload.",[1017,5787,5789],{"id":5788},"_4-enhanced-control-over-upload-logic","4. Enhanced Control Over Upload Logic",[117,5791,5792,5808],{},[110,5793,5794,5796,5797],{},[113,5795,5742],{},": Storing files locally before uploading to the cloud gives you complete control over the upload pipeline. You can:",[117,5798,5799,5802,5805],{},[110,5800,5801],{},"Batch uploads to the cloud.",[110,5803,5804],{},"Retry failed uploads without user intervention.",[110,5806,5807],{},"Implement custom file retention policies.",[110,5809,5810,5812],{},[113,5811,5748],{},": Handling large volumes of files in a controlled manner or ensuring uploads succeed under fluctuating network conditions.",[1017,5814,5816],{"id":5815},"_5-local-backups-for-safety","5. Local Backups for Safety",[117,5818,5819,5824],{},[110,5820,5821,5823],{},[113,5822,5742],{},": Keeping a local copy of uploaded files (even temporarily) can act as a fallback in case the cloud upload fails.",[110,5825,5826,5828],{},[113,5827,5748],{},": Critical applications where data loss is unacceptable.",[1017,5830,5832],{"id":5831},"_6-reduced-direct-cloud-costs","6. Reduced Direct Cloud Costs",[117,5834,5835,5840],{},[110,5836,5837,5839],{},[113,5838,5742],{},": Direct uploads to cloud services may involve high data transfer costs, especially if users upload files that need processing before final storage. Processing locally first can reduce unnecessary data transfer to the cloud.",[110,5841,5842,5844],{},[113,5843,5748],{},": Large file uploads where only the processed version needs to be stored.",[2573,5846,5848],{"id":5847},"trade-offs-of-using-multer-and-local-storage","Trade-Offs of Using Multer and Local Storage",[88,5850,5851],{},"While there are advantages, this approach has some trade-offs:",[1017,5853,5855],{"id":5854},"_1-increased-complexity","1. Increased Complexity",[117,5857,5858],{},[110,5859,5860],{},"Managing local file storage, cleanup, and subsequent cloud uploads introduces additional layers of logic.",[1017,5862,5864],{"id":5863},"_2-disk-space-requirements","2. Disk Space Requirements",[117,5866,5867],{},[110,5868,5869],{},"Storing files locally requires sufficient disk space, which can become an issue for large-scale applications.",[1017,5871,5873],{"id":5872},"_3-potential-data-loss","3. Potential Data Loss",[117,5875,5876],{},[110,5877,5878],{},"If the server crashes before files are uploaded to the cloud, you risk losing user-uploaded data.",[1017,5880,5882],{"id":5881},"_4-security-concerns","4. Security Concerns",[117,5884,5885],{},[110,5886,5887],{},"Keeping user files on the server, even temporarily, increases the risk of data breaches or accidental exposure.",[1017,5889,5891],{"id":5890},"_5-latency-for-cloud-upload","5. Latency for Cloud Upload",[117,5893,5894],{},[110,5895,5896],{},"The two-step process (uploading to local storage and then the cloud) adds a delay before the file is available in the cloud.",[2573,5898,5900],{"id":5899},"when-to-use-multer-for-local-uploads","When to Use Multer for Local Uploads?",[1017,5902,5904],{"id":5903},"developmenttesting","Development\u002FTesting:",[117,5906,5907,5910],{},[110,5908,5909],{},"Local development where cloud configuration isn't ready.",[110,5911,5912],{},"Testing image processing or validation logic.",[1017,5914,5916],{"id":5915},"file-preprocessing","File Preprocessing:",[117,5918,5919],{},[110,5920,5921],{},"When you need to process files before uploading (e.g., resizing, compression, encryption).",[1017,5923,5925],{"id":5924},"batch-uploads","Batch Uploads:",[117,5927,5928],{},[110,5929,5930],{},"Uploading large files in chunks or batches to reduce cloud API costs.",[1017,5932,5934],{"id":5933},"offline-or-intermittent-network","Offline or Intermittent Network:",[117,5936,5937],{},[110,5938,5939],{},"Use cases where the server may need to store files temporarily until network connectivity is restored.",{"title":165,"searchDepth":206,"depth":206,"links":5941},[5942,5950,5957],{"id":5730,"depth":206,"text":5731,"children":5943},[5944,5945,5946,5947,5948,5949],{"id":5734,"depth":212,"text":5735},{"id":5752,"depth":212,"text":5753},{"id":5772,"depth":212,"text":5773},{"id":5788,"depth":212,"text":5789},{"id":5815,"depth":212,"text":5816},{"id":5831,"depth":212,"text":5832},{"id":5847,"depth":206,"text":5848,"children":5951},[5952,5953,5954,5955,5956],{"id":5854,"depth":212,"text":5855},{"id":5863,"depth":212,"text":5864},{"id":5872,"depth":212,"text":5873},{"id":5881,"depth":212,"text":5882},{"id":5890,"depth":212,"text":5891},{"id":5899,"depth":206,"text":5900,"children":5958},[5959,5960,5961,5962],{"id":5903,"depth":212,"text":5904},{"id":5915,"depth":212,"text":5916},{"id":5924,"depth":212,"text":5925},{"id":5933,"depth":212,"text":5934},"2024-12-30","Multer offers benefits like easy debugging and processing flexibility but has trade-offs when transferring files to the cloud after local uploads","\u002Fimages\u002Fblog\u002Fusing-multer-for-local-file-uploads-benefits-trade-offs-and-cloud-transfer-integration.png",{},{"title":54,"description":5964},"A2wXt1k3VSU_fGIPu9VQXbES_KC7_MbEljcfRB3KcDk",{"id":5970,"title":46,"author":5971,"body":5973,"date":6221,"description":6222,"extension":1069,"image":6223,"meta":6224,"minRead":206,"navigation":73,"path":47,"seo":6225,"stem":48,"__hash__":6226},"blog\u002Fblog\u002Fnext-gen-e-learning-transforming-education-for-students-and-creators.md",{"name":80,"to":67,"username":81,"avatar":5972},{"src":83,"alt":80},{"type":85,"value":5974,"toc":6214},[5975,5979,5982,5984,5988,6040,6042,6046,6050,6082,6086,6112,6116,6142,6146,6166,6170,6183,6187,6207,6209,6211],[2573,5976,5978],{"id":5977},"vision","Vision",[88,5980,5981],{},"Our vision is to create a comprehensive e-learning platform that seamlessly connects students and creators. This platform will serve as a unified hub for live classes, recorded courses, community interaction, and effortless course transactions. By prioritizing engagement, accessibility, and monetization, we aim to empower creators while delivering a world-class learning experience for students.",[1014,5983],{},[1017,5985,5987],{"id":5986},"key-objectives","Key Objectives",[107,5989,5990,6000,6010,6020,6030],{},[110,5991,5992,5995],{},[113,5993,5994],{},"Interactive Learning",[117,5996,5997],{},[110,5998,5999],{},"Facilitate student participation in live classes with features such as chat, polls, and virtual whiteboards.",[110,6001,6002,6005],{},[113,6003,6004],{},"Comprehensive Content",[117,6006,6007],{},[110,6008,6009],{},"Provide creators with tools to upload recorded videos, create structured courses, and share resources.",[110,6011,6012,6015],{},[113,6013,6014],{},"Seamless Transactions",[117,6016,6017],{},[110,6018,6019],{},"Enable secure buying, selling, and subscribing to courses.",[110,6021,6022,6025],{},[113,6023,6024],{},"Community Engagement",[117,6026,6027],{},[110,6028,6029],{},"Promote collaboration through discussion forums, notifications, and networking opportunities.",[110,6031,6032,6035],{},[113,6033,6034],{},"Creator Empowerment",[117,6036,6037],{},[110,6038,6039],{},"Offer creators analytics, monetization options, and branding tools.",[1014,6041],{},[1017,6043,6045],{"id":6044},"core-features","Core Features",[92,6047,6049],{"id":6048},"_1-live-classes","1. Live Classes",[117,6051,6052,6058,6064,6070,6076],{},[110,6053,6054,6057],{},[113,6055,6056],{},"Real-Time Video Streaming",": Conduct live sessions seamlessly.",[110,6059,6060,6063],{},[113,6061,6062],{},"Interactive Tools",": Utilize polls, quizzes, live chat, and virtual whiteboards.",[110,6065,6066,6069],{},[113,6067,6068],{},"Breakout Rooms",": Facilitate group discussions within live sessions.",[110,6071,6072,6075],{},[113,6073,6074],{},"Raise Hand Feature",": Allow structured student interaction.",[110,6077,6078,6081],{},[113,6079,6080],{},"Live Transcription",": Enhance accessibility for diverse audiences.",[92,6083,6085],{"id":6084},"_2-recorded-courses","2. Recorded Courses",[117,6087,6088,6094,6100,6106],{},[110,6089,6090,6093],{},[113,6091,6092],{},"Secure Video Hosting",": Ensure DRM-protected video storage.",[110,6095,6096,6099],{},[113,6097,6098],{},"Content Organization",": Offer structured course modules with progress tracking.",[110,6101,6102,6105],{},[113,6103,6104],{},"Session Replay",": Record and edit live classes for later access.",[110,6107,6108,6111],{},[113,6109,6110],{},"Downloadable Resources",": Share slides, assignments, and additional materials.",[92,6113,6115],{"id":6114},"_3-course-selling-and-monetization","3. Course Selling and Monetization",[117,6117,6118,6124,6130,6136],{},[110,6119,6120,6123],{},[113,6121,6122],{},"Custom Pricing",": Allow creators to set course fees.",[110,6125,6126,6129],{},[113,6127,6128],{},"Payment Gateway Integration",": Support multiple payment options (e.g., Stripe, PayPal, Esewa).",[110,6131,6132,6135],{},[113,6133,6134],{},"Subscriptions",": Enable students to subscribe to creators for exclusive content.",[110,6137,6138,6141],{},[113,6139,6140],{},"Donations and Tips",": Allow students to support creators.",[92,6143,6145],{"id":6144},"_4-community-forums","4. Community Forums",[117,6147,6148,6154,6160],{},[110,6149,6150,6153],{},[113,6151,6152],{},"Discussion Boards",": Create topic-specific threads for collaborative learning.",[110,6155,6156,6159],{},[113,6157,6158],{},"Moderation Tools",": Provide admin and creator control over content.",[110,6161,6162,6165],{},[113,6163,6164],{},"Networking Opportunities",": Facilitate professional connections for students and creators.",[92,6167,6169],{"id":6168},"_5-notifications-and-updates","5. Notifications and Updates",[117,6171,6172,6177],{},[110,6173,6174,6176],{},[113,6175,2135],{},": Deliver real-time updates on live classes, new videos, and forum activity.",[110,6178,6179,6182],{},[113,6180,6181],{},"Email Alerts",": Send summaries of new content and upcoming events.",[92,6184,6186],{"id":6185},"_6-analytics-and-dashboards","6. Analytics and Dashboards",[117,6188,6189,6195,6201],{},[110,6190,6191,6194],{},[113,6192,6193],{},"Student Analytics",": Track learning progress, attendance, and activity.",[110,6196,6197,6200],{},[113,6198,6199],{},"Creator Insights",": Analyze course performance, earnings, and engagement metrics.",[110,6202,6203,6206],{},[113,6204,6205],{},"Admin Reports",": Monitor overall platform health and user activity.",[1014,6208],{},[1017,6210,1009],{"id":1008},[88,6212,6213],{},"The e-learning platform is designed to bridge the gap between students and creators, offering a robust ecosystem for learning and teaching. By combining live classes, recorded courses, community interaction, and monetization tools, it provides an all-in-one solution for modern education. With features like secure transactions, analytics, and community engagement, the platform not only supports creators in monetizing their expertise but also ensures an engaging and personalized learning experience for students. This scalable and user-focused platform is positioned to evolve with emerging needs, fostering a thriving e-learning community.",{"title":165,"searchDepth":206,"depth":206,"links":6215},[6216],{"id":5977,"depth":206,"text":5978,"children":6217},[6218,6219,6220],{"id":5986,"depth":212,"text":5987},{"id":6044,"depth":212,"text":6045},{"id":1008,"depth":212,"text":1009},"2024-12-07","Explore an e-learning platform empowering creators and enhancing student engagement with interactive, monetizable educational experiences","https:\u002F\u002Fcdn.hashnode.com\u002Fres\u002Fhashnode\u002Fimage\u002Fstock\u002Funsplash\u002FTamMbr4okv4\u002Fupload\u002F0cc897d3303f25e63e518849358d4cd4.jpeg",{},{"title":46,"description":6222},"aA_Z7OzDGClzBO1WvtXjUwPcCd-7xiU37P0nTe9uXbc"]