Get your free and exclusive 80-page Banking Passkey Report
react express crud app mysql

تطبيق CRUD باستخدام React وNode.js وExpress وMySQL

يشرح هذا الدليل التعليمي كيفية إنشاء تطبيق CRUD أساسي باستخدام React للواجهة الأمامية وExpress للواجهة الخلفية مع قاعدة بيانات MySQL.

Blog-Post-Author

Lukas

Created: June 17, 2025

Updated: June 24, 2025


في هذا البرنامج التعليمي الشامل، ستتعلم كيفية إنشاء تطبيق CRUD (إنشاء، قراءة، تحديث، حذف) متكامل (full-stack) باستخدام React للواجهة الأمامية وNode.js مع Express للواجهة الخلفية، وكل ذلك مدعوم بقاعدة بيانات MySQL. سنستخدم تقنيات مثل React Router وAxios وTailwindCSS لتحسين تجربة المستخدم.

Debugger Icon

Want to experiment with passkey flows? Try our Passkeys Debugger.

Try for Free

1. نظرة عامة على المشروع#

سنقوم بتطوير تطبيق متكامل حيث يمكن للمستخدمين إدارة الدروس التعليمية. كل درس تعليمي سيكون له السمات التالية:

  • المعرف (ID): معرّف فريد
  • العنوان (Title): اسم الدرس التعليمي
  • الوصف (Description): معلومات تفصيلية
  • حالة النشر (Published Status): تشير إلى ما إذا كان الدرس التعليمي منشورًا

يمكن للمستخدمين تنفيذ الإجراءات التالية:

  • إنشاء درس تعليمي جديد
  • استرداد جميع الدروس التعليمية أو درس محدد بواسطة المعرف
  • تحديث الدروس التعليمية الحالية
  • حذف الدروس التعليمية
  • البحث في الدروس التعليمية حسب العنوان
Subreddit Icon

Discuss passkeys news and questions in r/passkey.

Join Subreddit

فيما يلي، ستجد بعض لقطات الشاشة كأمثلة:

1.1 إضافة درس تعليمي جديد#

1.2 عرض جميع الدروس التعليمية#

Analyzer Icon

Are your users passkey-ready?

Test Passkey-Readiness

1.3 تعديل درس تعليمي#

1.4 البحث في الدروس التعليمية حسب العنوان#

Substack Icon

Subscribe to our Passkeys Substack for the latest news.

Subscribe

2. الهيكلية#

يتبع التطبيق هيكلية العميل-الخادم (client-server):

  • الواجهة الخلفية (Backend): يتعامل Node.js مع Express مع واجهات برمجة التطبيقات RESTful ويتفاعل مع قاعدة بيانات MySQL باستخدام Sequelize ORM.
  • الواجهة الأمامية (Frontend): يتواصل React.js مع الواجهة الخلفية عبر Axios لطلبات HTTP ويستخدم React Router للتنقل.

3. تنفيذ الواجهة الخلفية#

3.1 إعداد تطبيق Node.js#

  1. إنشاء مجلد المشروع:

    mkdir react-node-express-mysql-crud cd react-node-express-mysql-crud
  2. تهيئة تطبيق Node.js:

    npm init -y
  3. تثبيت الاعتماديات:

    npm install express sequelize mysql2 cors --save
  4. استخدام صيغة ESModule أضف السطر التالي إلى ملف package.json الخاص بك:

    { "type": "module" // ... }

3.2 إعداد MySQL و Sequelize#

  1. إنشاء ملف الإعدادات (app/config/db.config.js):
export default { HOST: "localhost", USER: "root", PASSWORD: "root", DB: "db", PORT: 8081, dialect: "mysql", pool: { max: 5, min: 0, acquire: 30000, idle: 10000, }, };
  1. تهيئة Sequelize (app/models/index.js):
import dbConfig from "../config/db.config.js"; import Sequelize from "sequelize"; import Tutorial from "./tutorial.model.js"; const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, { host: dbConfig.HOST, dialect: dbConfig.dialect, pool: dbConfig.pool, port: dbConfig.PORT, }); const db = {}; db.Sequelize = Sequelize; db.sequelize = sequelize; db.tutorials = Tutorial(sequelize, Sequelize); export default db;
  1. تعريف نموذج Tutorial (app/models/tutorial.model.js):
export default (sequelize, Sequelize) => { const Tutorial = sequelize.define("tutorial", { title: { type: Sequelize.STRING, }, description: { type: Sequelize.STRING, }, published: { type: Sequelize.BOOLEAN, }, }); return Tutorial; };

إذا لم يكن لديك قاعدة بيانات MySQL للتطوير المحلي، يمكنك استخدام Docker لإنشاء حاوية MySQL مؤقتة:

docker run --rm -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root \ -e MYSQL_DATABASE=db \ mysql:8

3.3 تعريف المسارات ووحدات التحكم (Controllers)#

  1. إنشاء وحدة التحكم (app/controllers/tutorial.controller.js):
import db from "../models/index.js"; const Op = db.Sequelize.Op; const Tutorial = db.tutorials; // Create and Save a new Tutorial export const create = (req, res) => { // Validate request if (!req.body.title) { res.status(400).send({ message: "Content can not be empty!", }); return; } // Create a Tutorial const tutorial = { title: req.body.title, description: req.body.description, published: req.body.published ? req.body.published : false, }; // Save Tutorial in the database Tutorial.create(tutorial) .then((data) => { res.send(data); }) .catch((err) => { res.status(500).send({ message: err.message || "Some error occurred while creating the Tutorial.", }); }); }; // Retrieve all Tutorials export const findAll = (req, res) => { // Allow a filter condition via query parameter const title = req.query.title; const condition = title ? { title: { [Op.like]: `%${title}%` } } : null; Tutorial.findAll({ where: condition }) .then((data) => { res.send(data); }) .catch((err) => { res.status(500).send({ message: err.message || "Some error occurred while retrieving tutorials.", }); }); }; // Find a single Tutorial by ID export const findOne = (req, res) => { const id = req.params.id; // Find Tutorial by primary key Tutorial.findByPk(id) .then((data) => { if (data) { res.send(data); } else { res.status(404).send({ message: `Cannot find Tutorial with id=${id}.`, }); } }) .catch((err) => { res.status(500).send({ message: "Error retrieving Tutorial with id=" + id, }); }); }; // Update a Tutorial by ID export const update = (req, res) => { const id = req.params.id; // Update the Tutorial with the specified ID Tutorial.update(req.body, { where: { id: id }, }) .then((num) => { if (num === 1) { res.send({ message: "Tutorial was updated successfully.", }); } else { res.send({ message: `Cannot update Tutorial with id=${id}. Maybe Tutorial was not found or req.body is empty!`, }); } }) .catch((err) => { res.status(500).send({ message: "Error updating Tutorial with id=" + id, }); }); }; // Delete a Tutorial by ID export const deleteOne = (req, res) => { const id = req.params.id; // Delete the Tutorial with the specified ID Tutorial.destroy({ where: { id: id }, }) .then((num) => { if (num === 1) { res.send({ message: "Tutorial was deleted successfully!", }); } else { res.send({ message: `Cannot delete Tutorial with id=${id}. Maybe Tutorial was not found!`, }); } }) .catch((err) => { res.status(500).send({ message: "Could not delete Tutorial with id=" + id, }); }); }; // Delete all Tutorials export const deleteAll = (req, res) => { // Delete all Tutorials Tutorial.destroy({ where: {}, truncate: false, }) .then((nums) => { res.send({ message: `${nums} Tutorials were deleted successfully!` }); }) .catch((err) => { res.status(500).send({ message: err.message || "Some error occurred while removing all tutorials.", }); }); }; // Find all published Tutorials export const findAllPublished = (req, res) => { // Find all Tutorials with published = true Tutorial.findAll({ where: { published: true } }) .then((data) => { res.send(data); }) .catch((err) => { res.status(500).send({ message: err.message || "Some error occurred while retrieving tutorials.", }); }); };
  1. إعداد المسارات (app/routes/tutorial.routes.js):
import * as tutorials from "../controllers/tutorial.controller.js"; import express from "express"; export default (app) => { let router = express.Router(); // Create a new Tutorial router.post("/", tutorials.create); // Retrieve all Tutorials router.get("/", tutorials.findAll); // Retrieve a single Tutorial with id router.get("/:id", tutorials.findOne); // Update a Tutorial with id router.put("/:id", tutorials.update); // Delete a Tutorial with id router.delete("/:id", tutorials.deleteOne); // Delete all Tutorials router.delete("/", tutorials.deleteAll); // Find all published Tutorials router.get("/published", tutorials.findAllPublished); app.use("/api/tutorials", router); };
Demo Icon

Want to try passkeys yourself in a passkeys demo?

Try Passkeys

3.4 تشغيل الخادم#

  1. إنشاء ملف الخادم (server.js):
import express from "express"; import cors from "cors"; import db from "./app/models.js"; import tutorialRoutes from "./app/routes/tutorial.routes.js"; const app = express(); const corsOptions = { origin: "http://localhost:5173", }; app.use(cors(corsOptions)); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Simple route app.get("/", (req, res) => { res.json({ message: "Welcome to the Tutorial Application." }); }); // Routes tutorialRoutes(app); // Sync database db.sequelize.sync().then(() => { console.log("Synced db."); }); const PORT = process.env.PORT || 8080; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}.`); });
  1. بدء تشغيل الخادم:
node server.js

الناتج:

Server is running on port 8080. Synced db.

4. تنفيذ الواجهة الأمامية#

هذه هي الهيكلية:

بدلاً من ذلك، يمكنك استخدام Redux:

4.1 هيكل الملفات#

سيبدو هيكل الملفات النهائي الخاص بك كما يلي:

frontend/ ├─ index.html ├─ package.json ├─ postcss.config.js ├─ tailwind.config.js ├─ vite.config.js ├─ src/ │ ├─ App.jsx │ ├─ main.jsx │ ├─ index.css │ ├─ services/ │ │ └─ tutorial.service.js │ └─ pages/ │ ├─ AddTutorial.jsx │ ├─ Tutorial.jsx │ └─ TutorialsList.jsx
Slack Icon

Become part of our Passkeys Community for updates & support.

Join

4.2 إنشاء تطبيق React#

قم بتشغيل الأوامر التالية لإعداد تطبيق React جديد باستخدام Vite:

npm create vite@latest frontend -- --template react cd frontend npm i npm i react-router-dom axios npm install -D tailwindcss autoprefixer npx tailwindcss init -p

أخيرًا، قم بإعداد خيار المحتوى (content) لـ tailwind في ملف الإعدادات tailwind.config.js:

/** @type {import('tailwindcss').Config} */ export default { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [], };

ثم، افتح src/index.css (لقد أنشأ Vite هذا الملف لك) وأضف توجيهات Tailwind:

@tailwind base; @tailwind components; @tailwind utilities;

4.3 تهيئة تخطيط التطبيق#

يقوم مكون App.jsx بتكوين مسارات React Router وإعداد شريط تنقل أساسي باستخدام Tailwind. سنتنقل بين:

  • /tutorials – قائمة الدروس التعليمية
  • /add – نموذج لإنشاء درس تعليمي جديد
  • /tutorials/:id – تعديل درس تعليمي واحد
import { Routes, Route, Link } from "react-router-dom"; import TutorialsList from "./pages/TutorialsList"; import AddTutorial from "./pages/AddTutorial"; import Tutorial from "./pages/Tutorial"; function App() { return ( <BrowserRouter> <div> {/* NAVBAR */} <nav className="bg-blue-600 p-4 text-white"> <div className="flex space-x-4"> <Link to="/tutorials" className="hover:text-gray-300 font-bold"> Tutorials </Link> <Link to="/add" className="hover:text-gray-300"> Add </Link> </div> </nav> {/* ROUTES */} <div className="container mx-auto mt-8 px-4"> <Routes> <Route path="/" element={<TutorialsList />} /> <Route path="/tutorials" element={<TutorialsList />} /> <Route path="/add" element={<AddTutorial />} /> <Route path="/tutorials/:id" element={<Tutorial />} /> </Routes> </div> </div> </BrowserRouter> ); } export default App;

4.4 إنشاء خدمة البيانات (Data Service)#

تتعامل هذه الخدمة مع طلبات HTTP من Axios إلى الواجهة الخلفية الخاصة بنا المبنية بـ Node/Express (http://localhost:8080/api). قم بتحديث baseURL إذا كان الخادم الخاص بك يعمل على عنوان أو منفذ مختلف.

import axios from "axios"; const http = axios.create({ baseURL: "http://localhost:8080/api", headers: { "Content-Type": "application/json", }, }); const getAll = () => { return http.get("/tutorials"); }; const get = (id) => { return http.get(`/tutorials/${id}`); }; const create = (data) => { return http.post("/tutorials", data); }; const update = (id, data) => { return http.put(`/tutorials/${id}`, data); }; const remove = (id) => { return http.delete(`/tutorials/${id}`); }; const removeAll = () => { return http.delete("/tutorials"); }; const findByTitle = (title) => { return http.get(`/tutorials?title=${title}`); }; export default { getAll, get, create, update, remove, removeAll, findByTitle, };

4.5 مكون إضافة عنصر#

مكون لإنشاء دروس تعليمية جديدة تحت المسار src/pages/AddTutorial.jsx. يسمح بإدخال العنوان والوصف، ثم يستدعي TutorialService.create().

import React, { useState } from "react"; import TutorialService from "../services/tutorial.service"; function AddTutorial() { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [submitted, setSubmitted] = useState(false); const saveTutorial = () => { const data = { title, description }; TutorialService.create(data) .then((response) => { console.log(response.data); setSubmitted(true); }) .catch((e) => { console.log(e); }); }; const newTutorial = () => { setTitle(""); setDescription(""); setSubmitted(false); }; return ( <div className="max-w-sm mx-auto p-4 bg-white rounded shadow"> {submitted ? ( <div> <h4 className="font-bold text-green-600 mb-4"> Tutorial submitted successfully! </h4> <button className="bg-blue-500 text-white px-3 py-1 rounded" onClick={newTutorial} > Add Another </button> </div> ) : ( <div> <h4 className="font-bold text-xl mb-2">Add Tutorial</h4> <div className="mb-2"> <label className="block mb-1 font-medium">Title</label> <input type="text" className="border border-gray-300 rounded w-full px-2 py-1" value={title} onChange={(e) => setTitle(e.target.value)} /> </div> <div className="mb-2"> <label className="block mb-1 font-medium">Description</label> <input type="text" className="border border-gray-300 rounded w-full px-2 py-1" value={description} onChange={(e) => setDescription(e.target.value)} /> </div> <button className="bg-green-500 text-white px-3 py-1 rounded mt-2" onClick={saveTutorial} > Submit </button> </div> )} </div> ); } export default AddTutorial;
Analyzer Icon

Are your users passkey-ready?

Test Passkey-Readiness

4.6 مكون قائمة الدروس التعليمية#

مكون تحت المسار src/pages/TutorialsList.jsx يقوم بما يلي:

  • يعرض شريط بحث للتصفية حسب عنوان الدرس التعليمي
  • يسرد الدروس التعليمية على اليسار
  • يعرض الدرس التعليمي المحدد على اليمين
  • يوفر زرًا لإزالة جميع الدروس التعليمية
import { useState, useEffect } from "react"; import TutorialService from "../services/tutorial.service"; import { Link } from "react-router-dom"; function TutorialsList() { const [tutorials, setTutorials] = useState([]); const [currentTutorial, setCurrentTutorial] = useState(null); const [currentIndex, setCurrentIndex] = useState(-1); const [searchTitle, setSearchTitle] = useState(""); useEffect(() => { retrieveTutorials(); }, []); const onChangeSearchTitle = (e) => { setSearchTitle(e.target.value); }; const retrieveTutorials = () => { TutorialService.getAll() .then((response) => { setTutorials(response.data); console.log(response.data); }) .catch((e) => { console.log(e); }); }; const refreshList = () => { retrieveTutorials(); setCurrentTutorial(null); setCurrentIndex(-1); }; const setActiveTutorial = (tutorial, index) => { setCurrentTutorial(tutorial); setCurrentIndex(index); }; const removeAllTutorials = () => { TutorialService.removeAll() .then((response) => { console.log(response.data); refreshList(); }) .catch((e) => { console.log(e); }); }; const findByTitle = () => { TutorialService.findByTitle(searchTitle) .then((response) => { setTutorials(response.data); setCurrentTutorial(null); setCurrentIndex(-1); console.log(response.data); }) .catch((e) => { console.log(e); }); }; return ( <div className="flex flex-col lg:flex-row gap-8"> {/* LEFT COLUMN: SEARCH + LIST */} <div className="flex-1"> <div className="flex mb-4"> <input type="text" className="border border-gray-300 rounded-l px-2 py-1 w-full" placeholder="Search by title" value={searchTitle} onChange={onChangeSearchTitle} /> <button className="bg-blue-500 text-white px-4 py-1 rounded-r" onClick={findByTitle} > Search </button> </div> <h4 className="font-bold text-lg mb-2">Tutorials List</h4> <ul className="divide-y divide-gray-200 border border-gray-200 rounded"> {tutorials && tutorials.map((tutorial, index) => ( <li className={ "px-4 py-2 cursor-pointer " + (index === currentIndex ? "bg-blue-100" : "") } onClick={() => setActiveTutorial(tutorial, index)} key={index} > {tutorial.title} </li> ))} </ul> <button className="bg-red-500 text-white px-3 py-1 rounded mt-4" onClick={removeAllTutorials} > Remove All </button> </div> {/* RIGHT COLUMN: DETAILS */} <div className="flex-1"> {currentTutorial ? ( <div className="p-4 bg-white rounded shadow"> <h4 className="font-bold text-xl mb-2">Tutorial</h4> <div className="mb-2"> <strong>Title: </strong> {currentTutorial.title} </div> <div className="mb-2"> <strong>Description: </strong> {currentTutorial.description} </div> <div className="mb-2"> <strong>Status: </strong> {currentTutorial.published ? "Published" : "Pending"} </div> <Link to={`/tutorials/${currentTutorial.id}`} className="inline-block bg-yellow-400 text-black px-3 py-1 rounded" > Edit </Link> </div> ) : ( <div> <p>Please click on a Tutorial...</p> </div> )} </div> </div> ); } export default TutorialsList;
StateOfPasskeys Icon

Want to find out how many people use passkeys?

View Adoption Data

4.7 مكون الدرس التعليمي (Tutorial Component)#

مكون وظيفي تحت المسار src/pages/Tutorial.jsx لعرض وتعديل درس تعليمي واحد. يستخدم:

  • useParams() للحصول على :id من عنوان URL
  • useNavigate() لإعادة التوجيه
  • TutorialService لعمليات الحصول والتحديث والحذف
import { useState, useEffect } from "react"; import { useParams, useNavigate } from "react-router-dom"; import TutorialService from "../services/tutorial.service"; function Tutorial() { const { id } = useParams(); const navigate = useNavigate(); const [currentTutorial, setCurrentTutorial] = useState({ id: null, title: "", description: "", published: false, }); const [message, setMessage] = useState(""); const getTutorial = (id) => { TutorialService.get(id) .then((response) => { setCurrentTutorial(response.data); console.log(response.data); }) .catch((e) => { console.log(e); }); }; useEffect(() => { if (id) getTutorial(id); }, [id]); const handleInputChange = (event) => { const { name, value } = event.target; setCurrentTutorial({ ...currentTutorial, [name]: value }); }; const updatePublished = (status) => { const data = { ...currentTutorial, published: status, }; TutorialService.update(currentTutorial.id, data) .then((response) => { setCurrentTutorial({ ...currentTutorial, published: status }); console.log(response.data); }) .catch((e) => { console.log(e); }); }; const updateTutorial = () => { TutorialService.update(currentTutorial.id, currentTutorial) .then((response) => { console.log(response.data); setMessage("The tutorial was updated successfully!"); }) .catch((e) => { console.log(e); }); }; const deleteTutorial = () => { TutorialService.remove(currentTutorial.id) .then((response) => { console.log(response.data); navigate("/tutorials"); }) .catch((e) => { console.log(e); }); }; return ( <div> {currentTutorial ? ( <div className="max-w-sm mx-auto p-4 bg-white rounded shadow"> <h4 className="font-bold text-xl mb-2">Edit Tutorial</h4> <div className="mb-2"> <label className="block font-medium" htmlFor="title"> Title </label> <input type="text" className="border border-gray-300 rounded w-full px-2 py-1" id="title" name="title" value={currentTutorial.title} onChange={handleInputChange} /> </div> <div className="mb-2"> <label className="block font-medium" htmlFor="description"> Description </label> <input type="text" className="border border-gray-300 rounded w-full px-2 py-1" id="description" name="description" value={currentTutorial.description} onChange={handleInputChange} /> </div> <div className="mb-2"> <strong>Status:</strong>{" "} {currentTutorial.published ? "Published" : "Pending"} </div> <div className="space-x-2 mt-2"> {currentTutorial.published ? ( <button className="bg-blue-500 text-white px-3 py-1 rounded" onClick={() => updatePublished(false)} > Unpublish </button> ) : ( <button className="bg-blue-500 text-white px-3 py-1 rounded" onClick={() => updatePublished(true)} > Publish </button> )} <button className="bg-red-500 text-white px-3 py-1 rounded" onClick={deleteTutorial} > Delete </button> <button className="bg-green-500 text-white px-3 py-1 rounded" onClick={updateTutorial} > Update </button> </div> {message && <p className="text-green-600 mt-2">{message}</p>} </div> ) : ( <div> <p>Loading tutorial...</p> </div> )} </div> ); } export default Tutorial;
Debugger Icon

Want to experiment with passkey flows? Try our Passkeys Debugger.

Try for Free

4.8 تشغيل التطبيق#

npm run dev

يمكنك الآن الوصول إلى التطبيق على http://localhost:5173. افتح هذا العنوان في متصفحك. يمكنك الآن التنقل إلى:

  • /tutorials – لرؤية جميع الدروس التعليمية
  • /add – لإضافة درس تعليمي
  • /tutorials/:id – لتعديل درس تعليمي محدد

(تأكد من أن الواجهة الخلفية Node/Express تعمل على http://localhost:8080 أو قم بتحديث baseURL في tutorial.service.js وفقًا لذلك.)

5. الخلاصة#

لقد قمت بنجاح ببناء تطبيق CRUD متكامل باستخدام React وNode.js وExpress وMySQL. يوضح هذا المشروع كيفية إعداد واجهة برمجة تطبيقات RESTful باستخدام Node.js وExpress، وإدارة البيانات باستخدام Sequelize ORM، وإنشاء واجهة أمامية سريعة الاستجابة باستخدام React وBootstrap.

برمجة سعيدة ونراكم في الدرس التعليمي التالي!

Add passkeys to your app in <1 hour with our UI components, SDKs & guides.

Start for free

Share this article


LinkedInTwitterFacebook

Enjoyed this read?

🤝 Join our Passkeys Community

Share passkeys implementation tips and get support to free the world from passwords.

🚀 Subscribe to Substack

Get the latest news, strategies, and insights about passkeys sent straight to your inbox.

Related Articles