[{"data":1,"prerenderedAt":594},["ShallowReactive",2],{"article-\u002Farticles\u002F685bf557c00f22daabc24699":3},{"id":4,"title":5,"articleId":6,"body":7,"cover":574,"coverThumb":575,"created":576,"date":577,"description":578,"extension":579,"meta":580,"navigation":581,"path":582,"seo":583,"stem":584,"tags":585,"updated":592,"__hash__":593},"articles\u002Farticles\u002F685bf557c00f22daabc24699.md","كتبنا الكود البرمجي لتطبيق \"مرتكز\" من الصفر!","685bf557c00f22daabc24699",{"type":8,"value":9,"toc":565},"minimark",[10,14,28,31,34,42,49,52,55,63,155,158,161,167,170,173,180,187,190,196,217,220,227,230,283,286,345,352,359,362,368,375,378,381,384,462,469,476,479,485,492,499,502,505,511,514,520,523,526,533,536,539,544,551,558,561],[11,12,13],"p",{},"الحمد لله الذي علَّم بالقلم، علَّم الإنسان ما لم يعلم، والصلاة والسلام على خير معلِّم الناس الخير سيدنا محمد - صلَّى الله عليه وسلم … أما بعد ،،",[11,15,16,17,27],{},"عندما بدأنا العمل على منصة \"",[18,19,26],"a",{"href":20,"rel":21,"target":25},"https:\u002F\u002Fmortakaz.com\u002F",[22,23,24],"nofollow","noopener","noreferrer","_blank","مرتكز","\"، كان الهدف واضحًا: إطلاق نسخة أولية بسيطة بأقل قدر ممكن من الخصائص الأساسية لقياس مدى تفاعل المستخدمين مع الفكرة.",[11,29,30],{},"وبفضل الله، جاءت النتائج مشجعة بشكل كبير، مما دفعنا لاتخاذ خطوة مهمة وهي إعادة كتابة الكود البرمجي بشكل منظّم وواضح، ليكون قابلًا للتوسع وإضافة ميزات جديدة بسهولة وثقة.",[11,32,33],{},"في هذا المقال، أشارك معك أبرز التحسينات التقنية التي قمنا بتطبيقها، إلى جانب تجربتنا الشخصية وأهم الدوافع وراء هذه التغييرات، آملين أن يكون ذلك مرجعًا نافعًا أو مصدر إلهام لك خلال عملك على مشاريعك الخاصة.",[35,36,38],"h2",{"id":37},"تنظيم-المنطق-عبر-services",[39,40,41],"strong",{},"تنظيم المنطق عبر Services",[11,43,44,45,48],{},"إن فصل المنطق في ملفات خدمات مستقلة (Services) هو نمط شائع يُستخدم في تطوير التطبيقات الحديثة، ويُعرف باسم ",[39,46,47],{},"Service Layer Pattern",".",[11,50,51],{},"هذا النمط يهدف إلى \"فصل مسؤوليات التطبيق\" بحيث لا تبقى العمليات المعقدة أو المتكررة مدمجة داخل مكونات الواجهة أو المعالجات (Controllers)، بل تُوضع في وحدات خارجية قابلة لإعادة الاستخدام والاختبار.",[11,53,54],{},"كمثال للتوضيح، قد تكون هناك عمليات متكررة مثل التحقق من جلسة المستخدم، أو تحميل البيانات من قاعدة البيانات، أو معالجة المدخلات.",[11,56,57,58,62],{},"بدلاً من كتابة هذه العمليات مباشرة في كل صفحة أو نقطة نهاية (API route)، يتم نقلها إلى ملفات مستقلة داخل مجلد ",[59,60,61],"code",{},"services\u002F"," مثل الكود التالي:",[64,65,70],"pre",{"className":66,"code":67,"language":68,"meta":69,"style":69},"language-javascript shiki shiki-themes github-dark","\u002F\u002F user.service.ts\nexport const getUserById = async (id: string) => {\n  return await UserModel.findById(id).lean()\n}\n","javascript","",[59,71,72,81,125,149],{"__ignoreMap":69},[73,74,77],"span",{"class":75,"line":76},"line",1,[73,78,80],{"class":79},"sAwPA","\u002F\u002F user.service.ts\n",[73,82,84,88,91,95,98,101,105,109,112,116,119,122],{"class":75,"line":83},2,[73,85,87],{"class":86},"snl16","export",[73,89,90],{"class":86}," const",[73,92,94],{"class":93},"svObZ"," getUserById",[73,96,97],{"class":86}," =",[73,99,100],{"class":86}," async",[73,102,104],{"class":103},"s95oV"," (",[73,106,108],{"class":107},"s9osk","id",[73,110,111],{"class":86},":",[73,113,115],{"class":114},"sDLfK"," string",[73,117,118],{"class":103},") ",[73,120,121],{"class":86},"=>",[73,123,124],{"class":103}," {\n",[73,126,128,131,134,137,140,143,146],{"class":75,"line":127},3,[73,129,130],{"class":86},"  return",[73,132,133],{"class":86}," await",[73,135,136],{"class":103}," UserModel.",[73,138,139],{"class":93},"findById",[73,141,142],{"class":103},"(id).",[73,144,145],{"class":93},"lean",[73,147,148],{"class":103},"()\n",[73,150,152],{"class":75,"line":151},4,[73,153,154],{"class":103},"}\n",[11,156,157],{},"تطبيق هذا الأسلوب جعل كود المشروع أكثر وضوحًا وقابلية للتوسع.",[11,159,160],{},"فعند إضافة ميزات جديدة أو تعديل سلوك معين، لم نعد بحاجة للبحث داخل ملفات متعددة، بل أصبحت معظم المهام المركزية منظمة داخل خدمات، مما وفّر علينا الوقت وقلل من فرص الوقوع في أخطاء ناتجة عن التكرار أو التداخل.",[35,162,164],{"id":163},"الاعتماد-الكامل-على-typescript",[39,165,166],{},"الاعتماد الكامل على Typescript",[11,168,169],{},"إن JavaScript تسمح بمرونة كبيرة في التعامل مع القيم والأنواع، لكنها لا تُنبهك عند تمرير متغير خاطئ (مثل إرسال نص بدل رقم).",[11,171,172],{},"أما Typescript، فيفرض عليك تحديد نوع كل متغير أو كائن أو دالة، ويُظهر خطأ في حال التناقض.",[11,174,175,176,179],{},"بالتالي عند الاعتماد الكامل على Typescript في المشروع، فإننا نضيف ",[39,177,178],{},"طبقة تحقق صارمة للأنواع (Static Type Checking)","، وهي ميزة رئيسية تساعد على كشف الأخطاء مبكرًا أثناء كتابة الكود، قبل حتى أن يُشغّل التطبيق.",[11,181,182,183,186],{},"إن تبني Typescript بالكامل في \"",[18,184,26],{"href":20,"rel":185,"target":25},[22,23,24],"\" قد ساعدنا على التحكم الأفضل في تدفق البيانات بين الواجهة الأمامية والخلفية، والتأكد من صحة القيم المستخدمة في كل خطوة.",[11,188,189],{},"هذا الأمر مكننا من إجراء تحسينات عميقة دون القلق من إتلاف شيء في مكان آخر، وقلل من زمن تتبع المشكلات في مرحلة التطوير.",[35,191,193],{"id":192},"وضوح-واستقرار-باستخدام-enums",[39,194,195],{},"وضوح واستقرار باستخدام Enums",[11,197,198,199,202,203,206,207,202,210,202,213,216],{},"في كثير من الحالات نحتاج إلى تمثيل مجموعة محددة من الخيارات، مثل أدوار المستخدمين (",[59,200,201],{},"admin",", ",[59,204,205],{},"user",") أو حالة المشروع (",[59,208,209],{},"pending",[59,211,212],{},"approved",[59,214,215],{},"rejected",").",[11,218,219],{},"وعند استخدام النصوص مباشرة، يمكن أن تحدث أخطاء بسبب اختلاف بسيط في الكتابة أو الاستخدام.",[11,221,222,223,226],{},"والحل هو استخدام ",[39,224,225],{},"Enums"," في Typescript، فهو وسيلة لتعريف مجموعة من القيم الثابتة والمتوقعة بشكل واضح ومقروء، مما يعزز من وضوح الكود ويقلل من الأخطاء الناتجة عن القيم النصية العشوائية أو المتكررة.",[11,228,229],{},"كمثال توضيحي لتطبيق ذلك علي أدوار المستخدمين:",[64,231,233],{"className":66,"code":232,"language":68,"meta":69,"style":69},"\u002F\u002F roles.enum.ts\nexport enum UserRole {\n  ADMIN = 'admin',\n  USER = 'user',\n}\n",[59,234,235,240,252,266,278],{"__ignoreMap":69},[73,236,237],{"class":75,"line":76},[73,238,239],{"class":79},"\u002F\u002F roles.enum.ts\n",[73,241,242,244,247,250],{"class":75,"line":83},[73,243,87],{"class":86},[73,245,246],{"class":86}," enum",[73,248,249],{"class":93}," UserRole",[73,251,124],{"class":103},[73,253,254,257,259,263],{"class":75,"line":127},[73,255,256],{"class":114},"  ADMIN",[73,258,97],{"class":86},[73,260,262],{"class":261},"sU2Wk"," 'admin'",[73,264,265],{"class":103},",\n",[73,267,268,271,273,276],{"class":75,"line":151},[73,269,270],{"class":114},"  USER",[73,272,97],{"class":86},[73,274,275],{"class":261}," 'user'",[73,277,265],{"class":103},[73,279,281],{"class":75,"line":280},5,[73,282,154],{"class":103},[11,284,285],{},"ثم نستخدمه داخل الكود بالشكل التالي:",[64,287,289],{"className":66,"code":288,"language":68,"meta":69,"style":69},"function checkPermission(role: UserRole) {\n  if (role === UserRole.ADMIN) {\n    \u002F\u002F allow access\n  }\n}\n",[59,290,291,312,331,336,341],{"__ignoreMap":69},[73,292,293,296,299,302,305,307,309],{"class":75,"line":76},[73,294,295],{"class":86},"function",[73,297,298],{"class":93}," checkPermission",[73,300,301],{"class":103},"(",[73,303,304],{"class":107},"role",[73,306,111],{"class":86},[73,308,249],{"class":93},[73,310,311],{"class":103},") {\n",[73,313,314,317,320,323,326,329],{"class":75,"line":83},[73,315,316],{"class":86},"  if",[73,318,319],{"class":103}," (role ",[73,321,322],{"class":86},"===",[73,324,325],{"class":103}," UserRole.",[73,327,328],{"class":114},"ADMIN",[73,330,311],{"class":103},[73,332,333],{"class":75,"line":127},[73,334,335],{"class":79},"    \u002F\u002F allow access\n",[73,337,338],{"class":75,"line":151},[73,339,340],{"class":103},"  }\n",[73,342,343],{"class":75,"line":280},[73,344,154],{"class":103},[11,346,347,348,351],{},"بهذا الشكل، إذا تم بالخطأ تمرير قيمة غير معرّفة داخل الـ ",[59,349,350],{},"enum","، سينبّهك Typescript بوجود خطأ قبل تشغيل التطبيق.",[11,353,354,355,358],{},"في \"",[18,356,26],{"href":20,"rel":357,"target":25},[22,23,24],"\"، استخدمنا Enums لتعريف أدوار المستخدمين، وأنواع التفاعل مع المشاريع، وحالات النشر وغيرها.",[11,360,361],{},"هذا وفّر وضوحًا كبيرًا أثناء التطوير، وقلّل من احتمالات الخطأ عند إرسال البيانات، كما جعل الكود أكثر مرونة في التوسع مستقبلًا دون الحاجة لمراجعة كل العناصر التي تستخدم هذه القيم.",[35,363,365],{"id":364},"استخدام-interfaces-لتعزيز-هيكلة-البيانات",[39,366,367],{},"استخدام Interfaces لتعزيز هيكلة البيانات",[11,369,370,371,374],{},"إن استخدام ",[39,372,373],{},"Interfaces"," في Typescript يعني تحديد شكل وهيكل البيانات بشكل واضح ومسبق.",[11,376,377],{},"فهي بمثابة \"عقد أو ميثاق\" يحدد ما يجب أن تحتويه الكائنات من خصائص، وما هي أنواع هذه الخصائص.",[11,379,380],{},"هذا يُساعد المطور على معرفة ما هو متوقع من البيانات، ويمنع تمرير كائنات غير مكتملة أو تحتوي على خصائص غير صحيحة.",[11,382,383],{},"لنقل أنك تتعامل مع كائن \"مستخدم\"، وتحتاج إلى التأكد أن كل جزء من التطبيق يعرف بالضبط ما هي خصائص هذا الكائن.",[64,385,389],{"className":386,"code":387,"language":388,"meta":69,"style":69},"language-typescript shiki shiki-themes github-dark","\u002F\u002F user.interface.ts\nexport interface IUser {\n  _id: string\n  firstName: string\n  lastName: string\n  email: string\n  role: UserRole\n}\n\n","typescript",[59,390,391,396,408,418,427,436,446,457],{"__ignoreMap":69},[73,392,393],{"class":75,"line":76},[73,394,395],{"class":79},"\u002F\u002F user.interface.ts\n",[73,397,398,400,403,406],{"class":75,"line":83},[73,399,87],{"class":86},[73,401,402],{"class":86}," interface",[73,404,405],{"class":93}," IUser",[73,407,124],{"class":103},[73,409,410,413,415],{"class":75,"line":127},[73,411,412],{"class":107},"  _id",[73,414,111],{"class":86},[73,416,417],{"class":114}," string\n",[73,419,420,423,425],{"class":75,"line":151},[73,421,422],{"class":107},"  firstName",[73,424,111],{"class":86},[73,426,417],{"class":114},[73,428,429,432,434],{"class":75,"line":280},[73,430,431],{"class":107},"  lastName",[73,433,111],{"class":86},[73,435,417],{"class":114},[73,437,439,442,444],{"class":75,"line":438},6,[73,440,441],{"class":107},"  email",[73,443,111],{"class":86},[73,445,417],{"class":114},[73,447,449,452,454],{"class":75,"line":448},7,[73,450,451],{"class":107},"  role",[73,453,111],{"class":86},[73,455,456],{"class":93}," UserRole\n",[73,458,460],{"class":75,"line":459},8,[73,461,154],{"class":103},[11,463,464,465,468],{},"الآن عند استخدام هذا ",[59,466,467],{},"interface"," في أي مكان داخل المشروع، فإن Typescript سيتحقق تلقائيًا من التزام الكائنات بهذا الشكل، وإذا نسيت خاصية أو كتبت نوعًا غير صحيح، سيظهر لك خطأ أثناء التطوير.",[11,470,471,472,475],{},"وفي \"",[18,473,26],{"href":20,"rel":474,"target":25},[22,23,24],"\"، ساعدنا استخدام الـ Interfaces في توحيد هيكل البيانات.",[11,477,478],{},"فعلى سبيل المثال، كل ما يتعلق بالمستخدمين أو المشاريع صار له شكل معروف وثابت، مما سهّل كتابة الكود، وتسريع اكتشاف الأخطاء، وأتاح لنا التركيز على منطق التطبيق بدلًا من تصحيح البيانات.",[35,480,482],{"id":481},"إدارة-الأخطاء-بشكل-مركزي",[39,483,484],{},"إدارة الأخطاء بشكل مركزي",[11,486,487,488,491],{},"في التطبيقات التقليدية، قد تضطر إلى استخدام ",[59,489,490],{},"try\u002Fcatch"," في كل وظيفة أو معالجة كل خطأ بطريقة مختلفة، مما يسبب فوضى وصعوبة في التتبع.",[11,493,494,495,498],{},"لكن مع الإدارة المركزية، يتم تعريف ",[39,496,497],{},"middle ware أو interceptor"," مسؤول عن استقبال الأخطاء من مختلف أنحاء المشروع، والتصرف بناءً على نوعها.",[11,500,501],{},"إن إدارة الأخطاء بشكل مركزي تعني أن يكون هناك مكان واحد مخصص للتعامل مع جميع الأخطاء التي قد تحدث أثناء تشغيل التطبيق، سواء في الواجهة الأمامية أو على مستوى الخادم (Back-end).",[11,503,504],{},"بدلاً من تكرار نفس الكود في كل وظيفة أو صفحة للتعامل مع الخطأ، يتم توجيه الخطأ إلى نقطة مركزية تقوم بتسجيله، تحليله، وإظهار رسالة مناسبة للمستخدم.",[11,506,471,507,510],{},[18,508,26],{"href":20,"rel":509,"target":25},[22,23,24],"\"، ساعدنا تطبيق إدارة الأخطاء مركزيًا على تحسين الاستجابة للأعطال بشكل واضح ومنظم.",[11,512,513],{},"ولم يعد هناك حاجة لتكرار التعامل مع الأخطاء في كل صفحة، بل أصبحت جميع أنواع الأخطاء تُجمع وتُحلل في مكان واحد، مما وفّر وقتًا كبيرًا في التطوير، وساعدني على تقديم تجربة أكثر احترافية واستقرارًا للمستخدم النهائي.",[35,515,517],{"id":516},"إعداد-قائمة-اختبار-بسيطة-smoke-tests",[39,518,519],{},"إعداد قائمة اختبار بسيطة Smoke Tests",[11,521,522],{},"تُعد اختبارات Smoke Tests وسيلة بسيطة وفعّالة لضمان أن الوظائف الأساسية للتطبيق ما تزال تعمل بشكل سليم بعد كل تحديث أو تعديل.",[11,524,525],{},"لا تهدف هذه الاختبارات إلى تغطية كل التفاصيل، بل تركز على النقاط الحيوية مثل تحميل الصفحة الرئيسية، تسجيل الدخول، أو عرض المحتوى، مما يساعد على اكتشاف الأعطال المبكرة ويوفّر الكثير من الوقت والجهد في المراحل المتقدمة.",[11,527,528,529,532],{},"في منصة \"",[18,530,26],{"href":20,"rel":531,"target":25},[22,23,24],"\"، اعتمدنا على قائمة تحقق بسيطة نقوم بمراجعتها بعد كل عملية نشر.",[11,534,535],{},"ورغم بساطتها، كانت كافية لمنحنا الثقة في أن التعديلات لم تؤثر سلبًا على التجربة الأساسية للمستخدم.",[11,537,538],{},"ربما لاحقًا - بإذن الله - نعمل على بناء نظام اختبارات تلقائي أكثر تطورًا، لكن في الوقت الحالي، هذا الأسلوب البسيط يؤدّي الغرض بكفاءة ويمنحنا أرضية مطمئنة لمواصلة التطوير بثبات.",[35,540,542],{"id":541},"الخاتمة",[39,543,541],{},[11,545,546,547,550],{},"رغم أن هذه التغييرات التقنية قد لا تكون ظاهرة بوضوح لمستخدمي منصة \"",[18,548,26],{"href":20,"rel":549,"target":25},[22,23,24],"\"، إلا أنها كانت ضرورية لوضع أساس متين يساعدنا علي التوسع بثقة، وتقديم تجربة أكثر استقرارًا وقابلية للتطوير في المستقبل.",[11,552,553,554,557],{},"إن \"",[18,555,26],{"href":20,"rel":556,"target":25},[22,23,24],"\" ليس مجرد مشروع تقني، بل رحلة نعمل عليها بشغف، نتعلم منها ونتطور من خلالها، ونسعى أن تكون إضافة حقيقية للمحتوى العربي والمجتمع التقني في منطقتنا.",[11,559,560],{},"وفقنا الله وإياك إلى ما يحبه ويرضاه ،،",[562,563,564],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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 pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":69,"searchDepth":83,"depth":127,"links":566},[567,568,569,570,571,572,573],{"id":37,"depth":83,"text":41},{"id":163,"depth":83,"text":166},{"id":192,"depth":83,"text":195},{"id":364,"depth":83,"text":367},{"id":481,"depth":83,"text":484},{"id":516,"depth":83,"text":519},{"id":541,"depth":83,"text":541},"\u002Fimages\u002F685bf5d8c00f22daabc246a4.webp","\u002Fimages\u002F685bf5d8c00f22daabc246a4_thumb.webp","2025-06-25T13:10:47.474Z","2025-06-25T13:20:06.515Z","مشاركة تجربتنا التقنية في إعادة كتابة الكود البرمجي لمنصة \"مرتكز\" من الصفر، مع توضيح أبرز التحسينات التي قمنا بتطبيقها، والدوافع خلف كل قرار تقني، بأسلوب عملي مبسّط يهدف إلى إفادة كل مطور يسعى لبناء مشروع أكثر استقرارًا.","md",{},true,"\u002Farticles\u002F685bf557c00f22daabc24699",{"title":5,"description":578},"articles\u002F685bf557c00f22daabc24699",[586,589],{"id":587,"label":588},"6593a0f940b5c90e5410e0a3","البرمجة",{"id":590,"label":591},"6838d556c00f22daabc20c15","منصة مرتكز","2025-06-25T13:23:21.091Z","W0ET0dob3TqNC7C0Im_uScZAgUseYRPBHG7_zv9jiU8",1788209571886]