مسارات10 دقايق ديال القراية

أول مشروع Node.js و React بالدارجة: API، CRUD و login بـ JWT

أول مشروع full stack خطوة بخطوة: React كيقرا API ديال Node.js و Express، مع useEffect، CRUD، الأخطاء، login بـ JWT و header Bearer.

كاين حتى ب : Français

Mehdi بلباس الأستاذ، غلاف المقال: React + Node.js

أول مشروع full stack ديالك يقدر يكون فجوج دوسيات: API بـ Node.js و Express كتجاوب بـ JSON، وfront بـ React كيستعملها. فدرس E28، React كيقرا، كيزيد، كيبدل وكيمسح المستخدمين من MySQL حقيقية، ومن بعد كيدخل بـ token ديال JWT. والـ API هي ديال الكورس ديال PHP لـ Node (E14)، تعاودات باش تجاوب بـ JSON، و le front مبني على الأساسيات ديال React فـ E27.

كيفاش نشغلو المشروع؟

خاصك Node.js 24 و MySQL 8؛ وإلا ما عندكش MySQL، docker compose up -d كيشعل la base من الدوسي ديال الـ repo.

sh
git clone https://github.com/Laraibi/lara-academy-react-api.git lara-react-api
cd lara-react-api/api
cp .env.example .env   # puis remplacer JWT_SECRET
npm ci
npm run db:init
npm run dev

وفترمينال آخر: cd web، npm ci، npm run dev. كل مرحلة فالفيديو عندها tag: git checkout ch03-effect كيحطك فالمرحلة ديال useEffect، و git diff ch03-effect ch04-etats كيوريك شنو تبدل.

علاش fetch وحدة كتصيفط آلاف ديال الطلبات؟

الدرس كيبدا بفخ معروف. Hamza كتب fetch وحدة باش يجيب المستخدمين، واللائحة بانت… ولكن Network كيحسب آلاف ديال الطلبات فشي ثواني.

الدورة، كيف شرحوها Bob و Hamza: React كيعيط على المكون باش يحسب شنو يبان، وكيلقى fetch؛ ملي كيجي الجواب، setUsers كتطلب عرض جديد؛ والعرض الجديد كيلقى fetch مرة أخرى. العرض خاصو غير يحسب الواجهة؛ والطلب للسيرفر خدمة أخرى، خاصها بلاصة بوحدها.

شنو كيتبدل ملي API كتجاوب بـ JSON؟

فالكورس ديال Node، السيرفر كان كيصيفط صفحة واجدة بـ res.render. دابا كيصيفط غير المعلومات بـ res.json، و React هو اللي كيقرر كيفاش تبان. و les routes كيتبعو les verbes ديال HTTP:

js
router.use(requireJwt);   // ajouté au chapitre Bearer

router.get('/', users.index);
router.post('/', users.store);
router.put('/:id', users.update);
router.delete('/:id', users.destroy);

مع الفورم ديال HTML، التعديل والمسح كانو بـ POST؛ ومع JSON ولينا كنستعملو PUT و DELETE. والـ JSON ديال المستخدمين فيه id و nom و email، وعمر الهاش ديال كلمة السر ما كيخرج من السيرفر: الـ model كيختار les colonnes.

كيفاش نوقفو الدورة بـ useEffect؟

الكود اللي فـ useEffect كيتنفذ من بعد ما تبان الواجهة. والطابلو اللي فالتالي هو لائحة dépendances: فيها المعلومات اللي إلا تبدلات كيتعاود التنفيذ. إلا كانت خاوية، كيتنفذ غير ملي كيبان المكون.

jsx
useEffect(() => {
  let ignore = false;

  async function loadUsers() {
    setLoading(true);
    setError('');
    try {
      const data = await request('/api/users', { token });
      if (!ignore) setUsers(data);
    } catch (loadError) {
      if (!ignore) setError(loadError.message);
    } finally {
      if (!ignore) setLoading(false);
    }
  }

  loadUsers();
  return () => { ignore = true; };
}, [token, attempt]);

3 حوايج شرحناهم فالدرس:

  • جوج طلبات فالتطوير ماشي bug. StrictMode كيبدا الخدمة، كينظفها، وكيعاود يبداها، باش يتأكد باللي ما خليناش شي حاجة موراها. فـ production كيبقى طلب واحد. وما تحيدش StrictMode باش تخبي هادشي.
  • ما تكتبش async مباشرة قدام الفونكسيون ديال useEffect: هاد الفونكسيون يا إما كترد فونكسيون التنظيف يا إما ما كترد والو، و async ديما كترد Promise. الحل: كتب فونكسيون async لداخل وعيط ليها.
  • ignore: التنظيف ديال الطلب الأول كيرد ignore صحيحة، وهكا الجواب ديالو ما كيبدلش state.

وعلاش fetch ماشي Axios؟ fetch كاين فالمتصفح بلا ما تزيد والو.

التحميل والأخطاء: علاش response.ok؟

Bob كيستعمل DevTools بحال لابو: F12، Network، وبلاصة « No throttling » كيختار 3G. كيعاود يحمل الصفحة: الشاشة خاوية. واش خسرات، ولا مازال كتحمل؟ المستخدم خاصو يعرف شنو واقع. كل طلب عندو 3 حالات: كنتسناو، وصلات المعلومات، ولا وقع مشكل. هادشي علاش زدنا state سميتها loading، وكنرجعوها false فـ finally. (وملي تسالي، رجع لـ « No throttling ».)

ومن بعد Bob كيطفي MySQL. الكل كيتسنى fetch تطيح فـ catch… ولكن Network كيقول 500، و fetch دازت عادي. السيرفر جاوب، إذن بالنسبة لـ fetch الطلب وصل. حنا اللي خاصنا نشوفو response.ok. وفالدرس جمعنا هاد الخدمة فـ فونكسيون سميتها request، فـ api.js:

js
export async function request(path, { token, method = 'GET', body } = {}) {
  const headers = {};
  if (body) headers['Content-Type'] = 'application/json';
  if (token) headers.Authorization = `Bearer ${token}`;

  const response = await fetch(path, {
    method,
    headers,
    body: body ? JSON.stringify(body) : undefined,
  });

  if (!response.ok) {
    const data = await response.json().catch(() => ({}));
    const error = new Error(data.error ?? `Erreur ${response.status}`);
    error.status = response.status;
    throw error;
  }
  return response.status === 204 ? null : response.json();
}

وفوقاش fetch كترفض بوحدها؟ ملي ما كيوصلش الجواب أصلا: حط Offline فالمتصفح، و Chrome كيقول Failed to fetch. وزر Réessayer كيزيد واحد لـ attempt، اللي كاينة فاللائحة ديال useEffect، إذن الطلب كيتعاود بلا ما تعاود تحمل الصفحة.

الإضافة والتعديل والمسح: فين نحطو الطلب؟

السؤال اللي طرح Mehdi: شنو اللي طلق هاد الطلب؟ ظهور الصفحة، ولا الكليك ديال المستخدم؟ فالإضافة، هو الكليك: إذن الطلب بلاصتو فـ handler ديال الفورم، ماشي فـ useEffect، وهادي نصيحة الـ documentation ديال React.

jsx
async function handleAdd(values) {
  const user = await request('/api/users', { token, method: 'POST', body: values });
  setUsers(previous => [...previous, user]);
}

async function handleSave(id, values) {
  const saved = await request(`/api/users/${id}`, { token, method: 'PUT', body: values });
  setUsers(previous => previous.map(user => (user.id === id ? saved : user)));
}

async function handleDelete(id) {
  await request(`/api/users/${id}`, { token, method: 'DELETE' });
  setUsers(previous => previous.filter(user => user.id !== id));
}
  • السيرفر كيرجع المستخدم الجديد بـ id ديالو: كنزيدوه لـ state بطابلو جديد، بحال السلة فـ E27، بلا ما نعاودو نجيبو اللائحة كاملة.
  • l'email ديال Sara مرة أخرى: UNIQUE فـ table كتعطي 409 والرسالة « Cet email existe déjà. ». الرسالة جات من السيرفر، و React غير عرضها، والفورم بقى عامر باش تصحح.
  • من بعد PUT، Bob كيعاود يحمل الصفحة: السمية الجديدة باقية، إذن السيرفر سجلها.
  • فـ DELETE، كنحيدو السطر من بعد الجواب ديال السيرفر. تقدر تحيدو من الأول باش يبان أسرع، ولكن إلا فشل الطلب خاصك ترجعو: فالبداية، خليها ساهلة.

وهنا Bob كيلاحظ: « ما دخلت حتى كلمة سر، ومسحت واحد من الناس. »

كيفاش كيخدم login بـ JWT؟

تفكير من الكورس ديال Node: session بحال بادج، والسيرفر عاقل عليه؛ وJWT كارطة موقعة، والكليان هو اللي كيهزها معاه.

js
export async function login(req, res) {
  const { email, password } = req.body;
  const user = email && password ? await User.findByEmailWithPassword(email) : undefined;
  const valid = user ? await bcrypt.compare(password, user.password) : false;
  if (!valid) return res.status(401).json({ error: 'Email ou mot de passe incorrect.' });

  const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
  res.json({ token, user: { id: user.id, nom: user.nom, email: user.email } });
}
  • l'email وكلمة السر كيمشيو بـ POST لـ /api/login، حيت هادي معلومات سرية ما خاصهاش تبان فـ URL.
  • token موقع، ماشي مخبي: أي واحد يقدر يقرا اللي فيه، إذن عمرك تحط فيه كلمة السر. هنا فيه غير userId، وصالح لساعة وحدة. و secret ديال التوقيع كيبقى فـ .env.
  • كلمة سر غالطة ولا email ما كاينش: نفس الرسالة. الفورم ديال login ما كيقولش لحتى واحد واش هاد l'email كاين.
  • فـ React، الجواب كيتحط فـ state سميناها session (غير سمية، ماشي session ديال السيرفر): إلا كانت عامرة، كتبان اللائحة؛ وإلا لا، كيبان الفورم.

ولكن Bob كيجرب: DELETE من الترمينال بـ curl، بلا token وبلا React… ودازت. login بوحدو ما كيحمي والو.

علاش 401 واخا أنا مكونيكطي؟

الحماية الحقيقية كتكون فالسيرفر. React كيبان فالمتصفح، وأي واحد يقدر يصيفط طلب بلا الواجهة ديالنا. كنزيدو سطر واحد، router.use(requireJwt)، ونفس curl كياخد 401.

js
export function requireJwt(req, res, next) {
  const [scheme, token] = (req.headers.authorization ?? '').split(' ');
  if (scheme !== 'Bearer' || !token) {
    return res.status(401).json({ error: 'Connexion requise.' });
  }
  try {
    req.userId = jwt.verify(token, process.env.JWT_SECRET).userId;
    next();
  } catch {
    res.status(401).json({ error: 'Connexion requise.' });
  }
}

والمفاجأة: من بعد ما عاود دخل، React حتى هو كياخد 401. token كاين فـ state… ولكن فـ Headers ديال الطلب، ما كاينش Authorization. token ما كيمشيش بوحدو: نتا اللي خاصك تحطو فالطلب. هادشي علاش زدنا Authorization: Bearer <token> فـ request، ودوزنا token من App لـ Users بـ props، وزدناه فاللائحة ديال useEffect.

وشنو شفنا حتى هو فالدرس:

  • إلا تبدل حرف واحد فـ token → 401؛
  • Déconnexion كتخوي state، ولكن token إلا شي واحد نسخو كيبقى صالح حتى تسالي المدة ديالو (ساعة هنا): ماشي بحال البادج ديال session اللي السيرفر يقدر يلغيه؛
  • ملي كتعاود تحمل الصفحة، state اللي فالميموار كتمشي، وكترجع للفورم. خلينا هادشي باش يبقى الكود واضح؛ و localStorage، إلا وقع XSS، أي JavaScript فالصفحة يقدر يقرا منو token: قرا على هادشي قبل ما تختار.

واش خاص useEffect باش نفيلتريو اللائحة؟

الشابتر الأخير: Bob بغا بحث، واقترح state سميتها filteredUsers كيبدلها useEffect. والسؤال ديال Mehdi: واش كاين شي طلب ولا شي حاجة برا React هنا؟ لا: النتيجة كتتحسب من users و query.

jsx
const search = query.trim().toLowerCase();
const visibleUsers = users.filter(user => user.nom.toLowerCase().includes(search));

القاعدة: حاجة كتتحسب، خليها فالعرض؛ حاجة جات من كليك، خليها فـ handler؛ و useEffect خليه للتزامن مع شي حاجة برا React، بحال الطلب ملي كتبان الصفحة.

أسئلة كيتعاودو

علاش جوج طلبات بلاصة واحد مع useEffect؟

فالتطوير، StrictMode كيبدا الـ effect، كينظفو، وكيعاود يبداه. وفـ production (npm run build ومن بعد npm run preview) كيبقى طلب واحد.

فين نخزنو token ديال JWT فـ React؟

فهاد الدرس، فـ state، فالميموار: كيمشي ملي كتعاود تحمل الصفحة. localStorage تذكر كسؤال، مع الخطر ديالو إلا وقع XSS، ولكن ما طبقناهش.

session ولا JWT؟

session كتبقى معروفة عند السيرفر، ويقدر يلغيها. و JWT كارطة موقعة كيهزها الكليان، وصالحة حتى تسالي المدة ديالها. الكورس ديال Node كيقدم بجوج، وهاد الدرس كيطبق JWT مع React.

واش المشروع فيه MongoDB ولا Axios؟

لا: الـ API كتستعمل MySQL، و le front كيستعمل fetch، بلا حتى dépendance من غير React.

شنو خاصك تحفظ

  • طلب ملي كتبان الصفحة بلاصتو فـ useEffect؛ طلب جا من كليك بلاصتو فـ handler.
  • fetch ما كترفضش فـ خطأ HTTP: شوف response.ok.
  • كل طلب فيه 3 حالات: تسنى، نجاح، ولا خطأ.
  • الحماية كتكون فالسيرفر (requireJwt)، ماشي غير بتخبية زر.
  • token خاصو يتزاد لكل طلب: Authorization: Bearer <token>.

شوف الدرس

الدرس كامل (14 دقيقة و41 ثانية، بالدارجة، مع Mehdi و Hamza و Bob) فالصفحة ديالو: React بالدارجة: نربطو API ديال Node (E28).

  • 00:00 fetch وحدة، وطلبات بلا نهاية
  • 00:31 السيرفر كيجاوب بـ JSON: من res.render لـ res.json
  • 01:25 useEffect: نوقفو الدورة
  • 03:21 ريزو بطيء بـ DevTools، التحميل، الأخطاء و response.ok
  • 06:12 الإضافة: POST و email مكرر (409)
  • 07:32 التعديل والمسح: PUT و DELETE
  • 08:54 Login: POST /api/login و token JWT
  • 10:52 Bearer و 401
  • 12:54 البحث: شنو ما خاصوش يكون فـ effect
  • 13:42 Récap والتحدي

المشروع كامل، مرحلة بمرحلة

  1. Node.js و Express بالدارجة: الـ API، MySQL، bcrypt و sessions (E14).
  2. E22: JavaScript، map، filter و reduce (كتحتاجهم ف React) (صفحة الحلقة).
  3. أول مشروع React: من JavaScript لسلّة مشتريات: JSX، props و useState (E27).
  4. هاد المقال: React كيقرا API، CRUD و login بـ JWT (E28).

المسار: تطوير الويب Full Stack.

زيد قدام

المصادر

Quiz: 3 أسئلة باش تتأكد

حتى حاجة ما كتصيفط: الأجوبة ديالك كيبقاو ف المتصفح ديالك.

  1. علاش fetch مكتوبة مباشرة وسط المكون كتصيفط طلبات بلا ما تحبس؟
  2. السيرفر جاوب بـ 500. شنو كدير fetch؟
  3. خبينا الزر ديال المسح فـ React. واش API ولات محمية؟

بارطاجي

تكتب من الدرس والنص ديالو بمساعدة أدوات الذكاء الاصطناعي، وراجعو وتأكد منو Mehdi Laraibi نهار 4 نونبر 2026.