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

React, Node.js, Express ve MySQL CRUD Uygulaması

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.

Blog-Post-Author

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.

Debugger Icon

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

Try for Free

1. Proje Genel Bakışı#

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:

  • ID: Benzersiz tanımlayıcı
  • Başlık: Eğitimin adı
  • Açıklama: Detaylı bilgi
  • Yayınlanma Durumu: Eğitimin yayınlanıp yayınlanmadığını belirtir

Kullanıcılar aşağıdaki eylemleri gerçekleştirebilir:

  • Yeni bir eğitim Oluşturma
  • Tüm eğitimleri veya ID'ye göre belirli birini Getirme
  • Mevcut eğitimleri Güncelleme
  • Eğitimleri Silme
  • Eğitimleri başlığa göre Arama
Subreddit Icon

Discuss passkeys news and questions in r/passkey.

Join Subreddit

Aşağıda bazı örnek ekran görüntüleri bulacaksınız:

1.1 Yeni Bir Eğitim Ekle#

1.2 Tüm Eğitimleri Görüntüle#

Analyzer Icon

Are your users passkey-ready?

Test Passkey-Readiness

1.3 Bir Eğitimi Düzenle#

1.4 Eğitimleri Başlığa Göre Ara#

Substack Icon

Subscribe to our Passkeys Substack for the latest news.

Subscribe

2. Mimari#

Uygulama, bir istemci-sunucu mimarisini takip eder:

  • Arka Yüz: Express ile Node.js, RESTful API'leri yönetir ve Sequelize ORM kullanarak MySQL veritabanı ile etkileşime girer.
  • Ön Yüz: React.js, HTTP istekleri için Axios aracılığıyla arka yüzle iletişim kurar ve gezinme için React Router kullanır.

3. Arka Yüz (Backend) Uygulaması#

3.1 Node.js Uygulamasını Kurma#

  1. Proje Dizini Oluşturun:

    mkdir react-node-express-mysql-crud cd react-node-express-mysql-crud
  2. Node.js Uygulamasını Başlatın:

    npm init -y
  3. Bağımlılıkları Yükleyin:

    npm install express sequelize mysql2 cors --save
  4. ESModule Sözdizimini Kullanın Aşağıdaki satırı package.json dosyanıza ekleyin:

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

3.2 MySQL ve Sequelize'ı Yapılandırma#

  1. Yapılandırma Dosyası Oluşturun (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'ı Başlatın (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 Modelini Tanımlayın (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

3.3 Rotaları (Routes) ve Kontrolcüleri (Controllers) Tanımlama#

  1. Kontrolcü Oluşturun (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.", }); }); };
  1. Rotaları Ayarlayın (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); };
Demo Icon

Want to try passkeys yourself in a passkeys demo?

Try Passkeys

3.4 Sunucuyu Çalıştırma#

  1. Sunucu Dosyası Oluşturun (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.`); });
  1. Sunucuyu Başlatın:
node server.js

Çıktı:

Sunucu 8080 portunda çalışıyor. Veritabanı senkronize edildi.

4. Ön Yüz (Frontend) Uygulaması#

Mimari şu şekildedir:

Alternatif olarak, Redux kullanabilirsiniz:

4.1 Dosya yapısı#

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
Slack Icon

Become part of our Passkeys Community for updates & support.

Join

4.2 React Uygulamasını Oluşturma#

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;

4.3 Uygulama Düzenini Başlatma#

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üzenleme
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"> 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;

4.4 Veri Servisi Oluşturma#

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, };

4.5 Öğe Ekleme Bileşeni#

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;
Analyzer Icon

Are your users passkey-ready?

Test Passkey-Readiness

4.6 Eğitim Listesi Bileşeni#

src/pages/TutorialsList.jsx altında yer alan ve şunları yapan bir bileşen:

  • Eğitim başlığına göre filtrelemek için bir arama çubuğu görüntüler
  • Solda eğitimleri listeler
  • Sağda seçilen eğitimi gösterir
  • Tüm eğitimleri kaldırmak için bir düğme sağlar
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;
StateOfPasskeys Icon

Want to find out how many people use passkeys?

View Adoption Data

4.7 Tutorial Bileşeni#

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:

  • URL'den :id'yi almak için useParams()
  • Yönlendirme yapmak için useNavigate()
  • get, update ve delete işlemleri için 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;
Debugger Icon

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

Try for Free

4.8 Uygulamayı Çalıştırma#

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.)

5. Sonuç#

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!

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