البرمجة

إنشاء مدونة باستخدام Express: التعليقات واللمسات النهائية

إنشاء مدوّنة باستخدام Express (الجزء 5): نظام التعليقات واللمسات النهائية

في هذا الجزء الخامس من سلسلة بناء مدوّنة متكاملة باستخدام إطار العمل Express في Node.js، سنتناول بشكل موسع كيفية إضافة نظام تعليقات متكامل للمقالات، بالإضافة إلى تطبيق اللمسات النهائية التي تجعل الموقع أكثر احترافية ومرونة في الاستخدام. يشمل هذا المقال شرحاً تفصيلياً لكل خطوة برمجية وفنية مع توضيح لهيكلة البيانات، طرق التعامل مع قواعد البيانات، معالجة الطلبات، وأخيراً تحسين تجربة المستخدم وتصميم الواجهة.


مقدمة

في عالم تطوير تطبيقات الويب، تعتبر المدونات منصة فعالة لنشر المحتوى وتبادل الأفكار مع الجمهور. إضافة نظام تعليقات يتيح للمستخدمين التفاعل مع المحتوى بشكل مباشر مما يعزز التواصل ويزيد من قيمة الموقع. ومع ذلك، فإن إنشاء نظام تعليقات متكامل يحتاج إلى مراعاة عدة جوانب فنية منها كيفية تخزين التعليقات، ربطها بالمقالات، إدارة المستخدمين، وأخيراً التعامل مع الأداء والأمان.

في هذا المقال، سنكمل المشروع السابق الذي بدأناه في الأجزاء السابقة ونتعلم كيفية بناء نظام تعليقات فعّال باستخدام Express، MongoDB، وEJS كقالب لعرض البيانات. سنتناول تفاصيل تنفيذ كل جزء بالشرح والبرمجة، مع الاهتمام بالتصميم النهائي.


1. هيكلة قاعدة البيانات لنظام التعليقات

الخطوة الأولى في بناء نظام التعليقات هي تحديد كيفية تمثيل التعليقات في قاعدة البيانات. نستخدم MongoDB لأنها تتناسب مع التطبيقات المرنة التي تحتاج إلى تغييرات سريعة، كما تتميز بسهولة التوسعة.

1.1 تصميم نموذج التعليق (Comment Schema)

يجب أن يحتوي نموذج التعليق على العناصر التالية:

  • نص التعليق (content)

  • معرف المقال الذي ينتمي إليه التعليق (postId)

  • اسم الكاتب أو معرف المستخدم (author)

  • تاريخ ووقت إنشاء التعليق (createdAt)

  • إمكانية الرد على تعليق آخر (اختياري، يمكن تضمين commentId للردود)

نموذج Mongoose للتعليق سيكون كالتالي:

js
const mongoose = require('mongoose'); const commentSchema = new mongoose.Schema({ content: { type: String, required: true, trim: true }, postId: { type: mongoose.Schema.Types.ObjectId, ref: 'Post', required: true }, author: { type: String, required: true }, createdAt: { type: Date, default: Date.now }, parentComment: { type: mongoose.Schema.Types.ObjectId, ref: 'Comment', default: null } }); module.exports = mongoose.model('Comment', commentSchema);

هذا التصميم يسمح بالتعليق الأساسي على المقالات بالإضافة إلى إمكانية إنشاء تعليقات فرعية كردود.


2. بناء واجهات برمجة التطبيقات (API) للتعليقات

بعد تصميم النموذج، يأتي دور إنشاء مسارات (Routes) في Express تسمح للمستخدمين بإرسال التعليقات، عرضها، وحذفها إذا كان لديهم صلاحية.

2.1 إضافة تعليق جديد

js
const express = require('express'); const router = express.Router(); const Comment = require('../models/Comment'); // إضافة تعليق جديد على مقال معين router.post('/comments/:postId', async (req, res) => { const { postId } = req.params; const { content, author, parentComment } = req.body; if (!content || !author) { return res.status(400).json({ error: 'المحتوى واسم الكاتب مطلوبان' }); } try { const newComment = new Comment({ content, postId, author, parentComment: parentComment || null }); await newComment.save(); res.status(201).json(newComment); } catch (error) { res.status(500).json({ error: 'حدث خطأ أثناء إضافة التعليق' }); } });

2.2 استرجاع التعليقات الخاصة بمقال معين

js
// استرجاع التعليقات المرتبطة بمقال معين router.get('/comments/:postId', async (req, res) => { const { postId } = req.params; try { const comments = await Comment.find({ postId }).sort({ createdAt: 1 }); res.json(comments); } catch (error) { res.status(500).json({ error: 'حدث خطأ أثناء جلب التعليقات' }); } });

3. ربط التعليقات بواجهة المستخدم

تأتي المرحلة التالية بتصميم واجهة المستخدم لعرض التعليقات وتقديمها باستخدام EJS أو أي محرك قوالب آخر.

3.1 عرض التعليقات في صفحة المقال

في قالب عرض المقال، يجب أن نقوم باستدعاء التعليقات الخاصة به وعرضها. يمكن فعل ذلك بإضافة استدعاء للـ API في صفحة المقال وتحميل التعليقات باستخدام جافاسكريبت أو عبر عملية الجلب من السيرفر.

مثال على جلب التعليقات في السيرفر وتمريرها للقالب:

js
// في ملف routes الخاص بالمقالات router.get('/posts/:id', async (req, res) => { try { const post = await Post.findById(req.params.id); const comments = await Comment.find({ postId: req.params.id }).sort({ createdAt: 1 }); res.render('post', { post, comments }); } catch (error) { res.status(500).send('خطأ في تحميل المقال'); } });

3.2 تصميم واجهة التعليقات في قالب EJS

ejs

التعليقات

<% comments.forEach(comment => { %>

<%= comment.author %> - <%= comment.createdAt.toLocaleString() %>

<%= comment.content %>

<% }) %>

4. تحسين تجربة المستخدم باستخدام AJAX

لجعل تجربة التعليقات أكثر سلاسة دون الحاجة إلى إعادة تحميل الصفحة، يمكن استخدام تقنية AJAX من خلال Fetch API في جافاسكريبت.

4.1 إرسال تعليق جديد بدون إعادة تحميل الصفحة

html
<script> document.querySelector('.comment-form').addEventListener('submit', async function(e) { e.preventDefault(); const author = this.author.value.trim(); const content = this.content.value.trim(); const postId = '<%= post._id %>'; if (!author || !content) return alert('الرجاء ملء جميع الحقول'); const response = await fetch(`/comments/${postId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ author, content }) }); if (response.ok) { const comment = await response.json(); const commentsSection = document.querySelector('.comments-section'); const commentDiv = document.createElement('div'); commentDiv.classList.add('comment'); commentDiv.innerHTML = `

${comment.author} - ${new Date(comment.createdAt).toLocaleString()}

${comment.content}

`; commentsSection.
appendChild(commentDiv); this.reset(); } else { alert('حدث خطأ أثناء إرسال التعليق'); } }); script
>

5. إضافة نظام الردود المتداخل (Nested Comments)

يمكن أن يكون نظام الردود معقداً عند إضافة خاصية الرد على تعليق معين. الطريقة المثلى هي عرض التعليقات بشكل متداخل بحيث تكون الردود ضمن التعليق الأصلي.

5.1 استخراج التعليقات مع تنظيم الردود

نحتاج إلى تنظيم التعليقات في شجرة تعتمد على الحقل parentComment:

js
function buildCommentTree(comments) { const map = {}; const roots = []; comments.forEach(comment => { map[comment._id] = { ...comment._doc, replies: [] }; }); comments.forEach(comment => { if (comment.parentComment) { map[comment.parentComment]?.replies.push(map[comment._id]); } else { roots.push(map[comment._id]); } }); return roots; }

نستخدم هذه الدالة في السيرفر قبل تمرير التعليقات إلى القالب:

js
const rawComments = await Comment.find({ postId }).sort({ createdAt: 1 }); const comments = buildCommentTree(rawComments); res.render('post', { post, comments });

5.2 عرض التعليقات المتداخلة في القالب

ejs
<% function renderComments(comments) { %>
    <% comments.forEach(comment => { %>
  • <%= comment.author %> - <%= comment.createdAt.toLocaleString() %>

    <%= comment.content %>

    <% if (comment.replies.length > 0) { %> <%= renderComments(comment.replies) %> <% } %>
  • <% }) %>
<% } %>

التعليقات

<%= renderComments(comments) %>

6. إضافة إمكانية حذف التعليقات وتعديلها

لزيادة المرونة، يمكن السماح للمستخدمين بحذف تعليقاتهم أو تعديلها إذا رغبوا. لتحقيق ذلك يجب:

  • التحقق من هوية المستخدم (باستخدام نظام تسجيل دخول)

  • توفير مسارات API للحذف والتعديل

  • تحديث الواجهة بما يتناسب مع هذه العمليات

مثال لمسار حذف تعليق:

js
router.delete('/comments/:id', async (req, res) => { const { id } = req.params; // هنا يجب إضافة التحقق من صلاحية المستخدم try { await Comment.findByIdAndDelete(id); res.json({ message: 'تم حذف التعليق' }); } catch (error) { res.status(500).json({ error: 'حدث خطأ أثناء حذف التعليق' }); } });

7. تحسينات نهائية على واجهة المستخدم

7.1 تنسيق CSS للتعليقات

استخدام تنسيقات CSS بسيطة لزيادة وضوح التعليقات، وخصوصاً التعليقات المتداخلة.

css
.comments-section { margin-top: 40px; } .comment-list { list-style: none; padding-left: 0; } .comment-list li { border: 1px solid #ddd; padding: 10px; margin-bottom: 10px; border-radius: 5px; } .comment-list ul { margin-left: 20px; margin-top: 10px; } .comment-form textarea { width: 100%; height: 100px; resize: vertical; } .comment-form input, .comment-form textarea, .comment-form button { margin-top: 10px; padding: 8px; font-size: 14px; }

7.2 تحسين الأداء

  • استخدام Pagination في حالة كثرة التعليقات لتجنب تحميل عدد هائل دفعة واحدة.

  • تخزين مؤقت (Caching) للتعليقات الأكثر قراءة.

  • تقليل حجم استجابات API عبر اختيار الحقول الضرورية فقط.


8. توسيع المشروع: إضافة ميزات متقدمة

8.1 نظام تسجيل دخول المستخدمين

لتحسين إدارة التعليقات، يمكن ربط التعليقات بحسابات المستخدمين الحقيقية، باستخدام JWT أو جلسات العمل (sessions). هذا يتيح إمكانية:

  • تعديل وحذف التعليقات للمستخدمين فقط.

  • التحقق من هوية الكاتب بدقة.

  • إضافة ميزات مثل الإعجاب بالتعليقات أو الإبلاغ عنها.

8.2 الإشعارات والتنبيهات

يمكن إضافة نظام إشعارات يُعلم الكاتب عند الرد على تعليقه أو عند وجود تعليقات جديدة في مقال معين، مما يزيد من التفاعل.

8.3 المراجعة والفلترة

إضافة نظام مراجعة التعليقات قبل نشرها، أو فلترة الكلمات المسيئة باستخدام مكتبات متخصصة، لضمان جودة المحتوى.


خاتمة

إن بناء نظام تعليقات فعال لمدوّنة باستخدام Express وMongoDB يمثل خطوة مهمة نحو تطوير منصة تفاعلية ومحترفة. من خلال تنظيم البيانات بشكل جيد، إنشاء مسارات API مرنة، ربطها بواجهة مستخدم سهلة الاستخدام، وتحسينات الأداء والتصميم، يصبح الموقع أكثر جاذبية للمستخدمين وأكثر قدرة على النمو والتطور.

المشروع يمكن توسيعه لاحقاً ليشمل ميزات متقدمة مثل نظام المستخدمين، الإشعارات، ومراقبة المحتوى، مما يجعل المدونة بيئة متكاملة للتواصل وتبادل الأفكار.


المصادر والمراجع


بهذا المقال تم تناول بناء نظام التعليقات واللمسات النهائية لمدوّنة باستخدام Express مع تفاصيل عملية وعلمية متعمقة، مما يوفر دليلاً شاملاً للمهتمين بتطوير تطبيقات ويب متقدمة.