Bu eğitim, ön yüzde React ve arka yüzde MySQL veritabanı ile Express kullanarak temel bir CRUD uygulamasının nasıl oluşturulacağını öğretmektedir.
Lukas
Created: June 17, 2025
Updated: June 24, 2025
Bu kapsamlı eğitimde, ön yüz için React ve arka yüz için Node.js ile Express kullanarak, tamamı bir MySQL veritabanı tarafından desteklenen tam teşekküllü bir CRUD (Oluştur, Oku, Güncelle, Sil) uygulaması oluşturmayı öğreneceksiniz. Kullanıcı deneyimini geliştirmek için React Router, Axios ve TailwindCSS gibi teknolojilerden yararlanacağız.
Kullanıcıların eğitimleri yönetebileceği tam teşekküllü bir uygulama geliştireceğiz. Her eğitimin aşağıdaki nitelikleri olacaktır:
Kullanıcılar aşağıdaki eylemleri gerçekleştirebilir:
Aşağıda bazı örnek ekran görüntüleri bulacaksınız:
Uygulama, bir istemci-sunucu mimarisini takip eder:
Proje Dizini Oluşturun:
mkdir react-node-express-mysql-crud cd react-node-express-mysql-crud
Node.js Uygulamasını Başlatın:
npm init -y
Bağımlılıkları Yükleyin:
npm install express sequelize mysql2 cors --save
ESModule Sözdizimini Kullanın Aşağıdaki satırı package.json dosyanıza ekleyin:
{ "type": "module" // ... }
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, }, };
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;
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; };
Yerel geliştirme için bir MySQL veritabanınız yoksa, geçici bir MySQL container'ı oluşturmak için Docker'ı kullanabilirsiniz:
docker run --rm -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root \ -e MYSQL_DATABASE=db \ mysql:8
app/controllers/tutorial.controller.js
):import db from "../models/index.js"; const Op = db.Sequelize.Op; const Tutorial = db.tutorials; // Yeni bir Tutorial oluştur ve kaydet export const create = (req, res) => { // İsteği doğrula if (!req.body.title) { res.status(400).send({ message: "İçerik boş olamaz!", }); return; } // Bir Tutorial oluştur const tutorial = { title: req.body.title, description: req.body.description, published: req.body.published ? req.body.published : false, }; // Tutorial'ı veritabanına kaydet Tutorial.create(tutorial) .then((data) => { res.send(data); }) .catch((err) => { res.status(500).send({ message: err.message || "Tutorial oluşturulurken bir hata oluştu.", }); }); }; // Tüm Tutorial'ları getir export const findAll = (req, res) => { // Sorgu parametresi aracılığıyla bir filtre koşuluna izin ver 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 || "Eğitimler alınırken bir hata oluştu.", }); }); }; // ID'ye göre tek bir Tutorial bul export const findOne = (req, res) => { const id = req.params.id; // Birincil anahtara göre Tutorial bul Tutorial.findByPk(id) .then((data) => { if (data) { res.send(data); } else { res.status(404).send({ message: `id=${id} olan Tutorial bulunamadı.`, }); } }) .catch((err) => { res.status(500).send({ message: "id=" + id + " olan Tutorial alınırken hata oluştu", }); }); }; // ID'ye göre bir Tutorial güncelle export const update = (req, res) => { const id = req.params.id; // Belirtilen ID'ye sahip Tutorial'ı güncelle Tutorial.update(req.body, { where: { id: id }, }) .then((num) => { if (num == 1) { res.send({ message: "Tutorial başarıyla güncellendi.", }); } else { res.send({ message: `id=${id} olan Tutorial güncellenemedi. Belki Tutorial bulunamadı veya req.body boş! `, }); } }) .catch((err) => { res.status(500).send({ message: "id=" + id + " olan Tutorial güncellenirken hata oluştu", }); }); }; // ID'ye göre bir Tutorial sil export const deleteOne = (req, res) => { const id = req.params.id; // Belirtilen ID'ye sahip Tutorial'ı sil Tutorial.destroy({ where: { id: id }, }) .then((num) => { if (num == 1) { res.send({ message: "Tutorial başarıyla silindi!", }); } else { res.send({ message: `id=${id} olan Tutorial silinemedi. Belki Tutorial bulunamadı!`, }); } }) .catch((err) => { res.status(500).send({ message: "id=" + id + " olan Tutorial silinemedi", }); }); }; // Tüm Tutorial'ları sil export const deleteAll = (req, res) => { // Tüm Tutorial'ları sil Tutorial.destroy({ where: {}, truncate: false, }) .then((nums) => { res.send({ message: `${nums} adet Tutorial başarıyla silindi!` }); }) .catch((err) => { res.status(500).send({ message: err.message || "Tüm eğitimler kaldırılırken bir hata oluştu.", }); }); }; // Yayınlanmış tüm Tutorial'ları bul export const findAllPublished = (req, res) => { // published = true olan tüm Tutorial'ları bul Tutorial.findAll({ where: { published: true } }) .then((data) => { res.send(data); }) .catch((err) => { res.status(500).send({ message: err.message || "Eğitimler alınırken bir hata oluştu.", }); }); };
app/routes/tutorial.routes.js
):import * as tutorials from "../controllers/tutorial.controller.js"; import express from "express"; export default (app) => { let router = express.Router(); // Yeni bir Tutorial oluştur router.post("/", tutorials.create); // Tüm Tutorial'ları getir router.get("/", tutorials.findAll); // id ile tek bir Tutorial getir router.get("/:id", tutorials.findOne); // id ile bir Tutorial güncelle router.put("/:id", tutorials.update); // id ile bir Tutorial sil router.delete("/:id", tutorials.deleteOne); // Tüm Tutorial'ları sil router.delete("/", tutorials.deleteAll); // Yayınlanmış tüm Tutorial'ları bul router.get("/published", tutorials.findAllPublished); app.use("/api/tutorials", router); };
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 })); // Basit rota app.get("/", (req, res) => { res.json({ message: "Eğitim Uygulamasına Hoş Geldiniz." }); }); // Rotalar tutorialRoutes(app); // Veritabanını senkronize et db.sequelize.sync().then(() => { console.log("Veritabanı senkronize edildi."); }); const PORT = process.env.PORT || 8080; app.listen(PORT, () => { console.log(`Sunucu ${PORT} portunda çalışıyor.`); });
node server.js
Çıktı:
Sunucu 8080 portunda çalışıyor. Veritabanı senkronize edildi.
Mimari şu şekildedir:
Alternatif olarak, Redux kullanabilirsiniz:
Son dosya yapınız şöyle görünecektir:
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
Vite kullanarak yeni bir React uygulaması kurmak için aşağıdaki komutları çalıştırın:
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
Son olarak, tailwind.config.js
yapılandırma dosyasında tailwind content seçeneğini
ayarlayın:
/** @type {import('tailwindcss').Config} */ export default { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [], };
Ardından, src/index.css dosyasını açın (Vite bu dosyayı sizin için oluşturdu) ve Tailwind direktiflerini ekleyin:
@tailwind base; @tailwind components; @tailwind utilities;
App.jsx
bileşeni, React Router rotalarını yapılandırır ve temel bir Tailwind navbar'ı
kurar. Şunlar arasında gezineceğiz:
/tutorials
– eğitimlerin listesi/add
– yeni eğitim oluşturma formu/tutorials/:id
– tek bir eğitimi düzenlemeimport { 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"> Eğitimler </Link> <Link to="/add" className="hover:text-gray-300"> Ekle </Link> </div> </nav> {/* ROTALAR */} <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;
Bu servis, Node/Express arka yüzümüze (http://localhost:8080/api) yapılan Axios HTTP isteklerini yönetir. Sunucunuz farklı bir adreste veya portta çalışıyorsa baseURL'i güncelleyin.
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, };
src/pages/AddTutorial.jsx
altında yeni eğitimler oluşturmak için bir bileşen. Başlık ve
açıklama girilmesine olanak tanır ve ardından TutorialService.create() fonksiyonunu
çağırır.
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"> Eğitim başarıyla gönderildi! </h4> <button className="bg-blue-500 text-white px-3 py-1 rounded" onClick={newTutorial} > Başka Ekle </button> </div> ) : ( <div> <h4 className="font-bold text-xl mb-2">Eğitim Ekle</h4> <div className="mb-2"> <label className="block mb-1 font-medium">Başlık</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">Açıklama</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} > Gönder </button> </div> )} </div> ); } export default AddTutorial;
src/pages/TutorialsList.jsx
altında yer alan ve şunları yapan bir bileşen:
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"> {/* SOL SÜTUN: ARAMA + LİSTE */} <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="Başlığa göre ara" value={searchTitle} onChange={onChangeSearchTitle} /> <button className="bg-blue-500 text-white px-4 py-1 rounded-r" onClick={findByTitle} > Ara </button> </div> <h4 className="font-bold text-lg mb-2">Eğitim Listesi</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} > Tümünü Kaldır </button> </div> {/* SAĞ SÜTUN: DETAYLAR */} <div className="flex-1"> {currentTutorial ? ( <div className="p-4 bg-white rounded shadow"> <h4 className="font-bold text-xl mb-2">Eğitim</h4> <div className="mb-2"> <strong>Başlık: </strong> {currentTutorial.title} </div> <div className="mb-2"> <strong>Açıklama: </strong> {currentTutorial.description} </div> <div className="mb-2"> <strong>Durum: </strong> {currentTutorial.published ? "Yayınlandı" : "Beklemede"} </div> <Link to={`/tutorials/${currentTutorial.id}`} className="inline-block bg-yellow-400 text-black px-3 py-1 rounded" > Düzenle </Link> </div> ) : ( <div> <p>Lütfen bir Eğitim'e tıklayın...</p> </div> )} </div> </div> ); } export default TutorialsList;
src/pages/Tutorial.jsx
altında tek bir eğitimi görüntülemek ve düzenlemek için bir
fonksiyonel bileşen. Şunları kullanır:
useParams()
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("Eğitim başarıyla güncellendi!"); }) .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">Eğitimi Düzenle</h4> <div className="mb-2"> <label className="block font-medium" htmlFor="title"> Başlık </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"> Açıklama </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>Durum:</strong>{" "} {currentTutorial.published ? "Yayınlandı" : "Beklemede"} </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)} > Yayından Kaldır </button> ) : ( <button className="bg-blue-500 text-white px-3 py-1 rounded" onClick={() => updatePublished(true)} > Yayınla </button> )} <button className="bg-red-500 text-white px-3 py-1 rounded" onClick={deleteTutorial} > Sil </button> <button className="bg-green-500 text-white px-3 py-1 rounded" onClick={updateTutorial} > Güncelle </button> </div> {message && <p className="text-green-600 mt-2">{message}</p>} </div> ) : ( <div> <p>Eğitim yükleniyor...</p> </div> )} </div> ); } export default Tutorial;
npm run dev
Artık uygulamaya http://localhost:5173 adresinden erişebilirsiniz. Bu URL'yi tarayıcınızda açın. Artık şuralara gidebilirsiniz:
/tutorials
– tüm eğitimleri gör/add
– bir eğitim ekle/tutorials/:id
– belirli bir eğitimi düzenle(Node/Express arka yüzünüzün http://localhost:8080 üzerinde çalıştığından emin olun veya tutorial.service.js dosyasındaki baseURL'i buna göre güncelleyin.)
React, Node.js, Express ve MySQL kullanarak tam teşekküllü bir CRUD uygulamasını başarıyla oluşturdunuz. Bu proje, Node.js ve Express ile bir RESTful API'nin nasıl kurulacağını, Sequelize ORM ile verilerin nasıl yönetileceğini ve React ve TailwindCSS ile duyarlı bir ön yüzün nasıl oluşturulacağını göstermektedir.
İyi kodlamalar ve bir sonraki eğitimde görüşmek üzere!
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
Table of Contents