Node.js ve MongoDB Kullanarak Kategorilere Sahip Basit Bir Görev Yöneticisi Oluşturun
Node.js ve MongoDB ile görevleri kategoriye göre düzenlemeyi öğrenin. Oluşturma, listeleme, getirme, güncelleme, silme uç noktaları ve kategori filtresi olan bir REST API kurun.
Düz bir yapılacaklar listesi başlangıçta gayet işe yarar: birkaç görev eklersiniz ve bitirdiğinizde kapatabilirsiniz. Ancak görevler artmaya devam ettikçe kullanımı zorlaşır. Bunu düzeltmenin bir yolu kategorilendirme eklemektir; böylece her görev İş, Çalışma, Kişisel vb. kategorilere ait olabilir ve listeyi ilgili kategoriye göre filtreleyebilirsiniz.
Bu eğitimde, Node.js ve MongoDB kullanarak görevleri kategoriye göre düzenlemenize olanak tanıyan bir REST API’nin nasıl oluşturulacağını öğreneceksiniz. Sonunda, oluşturma, listeleme, getirme, güncelleme ve silme olmak üzere beş uç noktanın yanı sıra bir kategori filtresine sahip çalışan bir Node.js sunucunuz olacak. Ayrıca, Mongoose gibi bir ODM’ye bağlı kalmadan yerel MongoDB sürücüsünü kullanarak kategori doğrulamasını nasıl zorunlu kılacağınızı da göreceksiniz.
Neler Öğreneceksiniz
Node.js, Express ve MongoDB ile tam CRUD uç noktalarını içeren bir REST API’nin nasıl oluşturulacağı
Girdilerin nasıl doğrulanacağı, kategorilerin nasıl zorunlu kılınacağı ve ObjectId değerlerinin güvenli biçimde nasıl işleneceği
API’nizin nasıl yapılandırılıp test edileceği ve ardından bir ön yüze nasıl bağlanacağı
Bu eğitimin tüm kodunu GitHub’da bulabilirsiniz; isterseniz klonlayıp eş zamanlı olarak okuyabilirsiniz.
Önkoşullar
Başlamadan önce şunlara sahip olmalısınız:
Node.js 18+ kurulu (npm varsayılan olarak dahildir)
Yerelde çalışan MongoDB veya ücretsiz bir MongoDB Atlas kümesi
Postman veya herhangi bir HTTP istemcisi
Temel düzeyde JavaScript ve REST kavramları bilgisi
Adım 1: Projeyi Kurun
Basit görev yöneticimizi oluşturmaya başlayalım. İlk yapacağımız şey yeni bir klasör oluşturup terminalimizde aşağıdaki komutları çalıştırarak bir Node projesi başlatmak olacaktır:
mkdir task-manager-categories
cd task-manager-categories
npm init -yAltyapı:
Sırada, uygulamanın çalışması için gereken dört çalışma zamanı bağımlılığını kuracağız: yönlendirme için express, resmi MongoDB sürücüsü, ortam değişkenlerini yüklemek için dotenv ve son olarak güvenlik başlıkları için helmet.
Sonrasında, proje klasörünü IDE’nizde açın ve package.json içindeki scripts bölümünü aşağıdakileri içerecek şekilde güncelleyin. Bu sayede sunucuyu npm start ile başlatabilir veya geliştirme modunda npm run dev ile çalıştırabilirsiniz:
İlerledikçe oluşturacağımız yapı aşağıdaki gibi olacak. Her klasörün belirli bir rolü var: db/ veritabanı bağlantısını yönetir, lib/ yardımcı işlevleri içerir, middleware/ Express ara yazılımları içindir ve routes/ istek işleyicilerini tanımlar. public/ klasörü ise daha sonra ekleyeceğimiz küçük bir ön yüzü barındıracak. Küçük bir projede bile bu yapı işleri takip etmeyi kolaylaştırır ve yeni kodun nereye eklenmesi gerektiğini netleştirir. İsterseniz klasör yapısını şimdi oluşturup ilerledikçe doldurun ya da bunu atlayıp adımlar boyunca her dosyayı sırasıyla ekleyin:
Görevleri depolamak ve almak için önce uygulamamızı MongoDB’ye bağlamamız gerekir. Bu, MongoDB sürücüsüne veritabanınıza nasıl bağlanacağını söyleyen bir bağlantı dizesi kullanılarak yapılır. Bunu doğrudan kodunuza gömmek yerine bir ortam dosyasında tutmak daha iyidir. Bu yaklaşım hassas değerleri kod tabanınızın dışında tutar ve ortamlar arasında geçişi kolaylaştırır.
Gerekli değişkenleri belgelemek için bir .env.example dosyası ve gerçek değerleriniz için bir .env dosyası oluşturun, şöyle:
# Copy this file to `.env` and fill in real values.# --- Local MongoDB ---# Use this if you're running MongoDB locallyMONGO_URI=mongodb://127.0.0.1:27017
# --- MongoDB Atlas ---# Replace <username>, <password>, and <cluster-url> with your actual values# Example: mongodb+srv://user:[email protected]/# MONGO_URI=mongodb+srv://<username>:<password>@<cluster-url>/?retryWrites=true&w=majorityDB_NAME=taskmanager
PORT=3000Altyapı:
Artık bir bağlantı dizesi hazır olduğuna göre, bunu MongoDB bağlantımızı kurmak için kullanabiliriz. Bir db/connect.js dosyası oluşturun. MongoDB istemcisini başlatacağımız ve uygulamanın geri kalanına erişilebilir kılacağımız yer burası olacak:
const{ MongoClient }=require("mongodb");let client;let db;asyncfunctionconnectDB(){if(!process.env.MONGO_URI){thrownewError("MONGO_URI is not set. Check your .env file.");}
client =newMongoClient(process.env.MONGO_URI,{appName:"devrel-tutorial-javascript-crud-geeksforgeeks",});await client.connect();
db = client.db(process.env.DB_NAME||"taskmanager");await db.collection("tasks").createIndex({category:1});
console.log(`MongoDB connected (db: ${db.databaseName})`);}functiongetTasksCollection(){if(!db){thrownewError("Database not initialized. Call connectDB() first.");}return db.collection("tasks");}asyncfunctioncloseDB(){if(client)await client.close();}
module.exports ={ connectDB, getTasksCollection, closeDB };Altyapı:
Bu dosya, uygulamanın geri kalanının yeniden kullanabileceği tek bir MongoDB istemcisi kurar. Bunu yalnızca bir kez oluşturmak istersiniz; çünkü sürücü zaten arka planda bağlantı havuzlamayı yönetir. Her istek için yeni bir istemci oluşturmak ilk başta sorun değilmiş gibi görünse de kısa sürede performans sorunlarına yol açar.
Adım 3: Görev Yapısını ve Doğrulamayı Tanımlayın
Bu noktada uygulama MongoDB’ye bağlanabiliyor. Şimdi, herhangi bir şey kaydetmeye başlamadan önce bir görevin gerçekte nasıl göründüğünü tanımlamamız gerekiyor.
Yerel MongoDB sürücüsünü kullanmak burada biraz farklı hissettirmeye başlar. Mongoose’daki gibi bir şema dosyası yok. Bunun yerine “şema”, veritabanına eklediğiniz nesnenin biçimidir. İlk başta biraz gevşek gelebilir, ancak aslında faydalıdır; çünkü MongoDB’nin gerçekte ne yaptığına yakın kalırsınız ve hiçbir şey soyutlamaların arkasına saklanmaz.
Yine de doğrulama istiyoruz. Bu nedenle bu mantığı farklı rotalara dağıtmak yerine tek bir yerde tutacağız; böylece her şey aynı kurallara uyar. lib/taskDocument.js adlı bir dosya oluşturun ve aşağıdaki kodu ekleyin:
classValidationErrorextendsError{constructor(message){super(message);this.name ='ValidationError';}}constALLOWED_CATEGORIES=['Work','Personal','Study','Other'];functionassertValidCategory(category){if(!ALLOWED_CATEGORIES.includes(category)){thrownewValidationError(`category must be one of: ${ALLOWED_CATEGORIES.join(', ')}`);}}functionbuildTaskDocument(body ={}){if(!body.title ||typeof body.title !=='string'||!body.title.trim()){thrownewValidationError('title is required and must be a non-empty string');}if(body.category !=null){assertValidCategory(body.category);}const now =newDate();return{title: body.title.trim(),description:typeof body.description ==='string'? body.description.trim():'',category: body.category !=null? body.category :'Other',completed:Boolean(body.completed),createdAt: now,updatedAt: now
};}functionbuildTaskUpdate(body ={}){const updates ={};if(typeof body.title ==='string'&& body.title.trim()){
updates.title = body.title.trim();}if(typeof body.description ==='string'){
updates.description = body.description.trim();}if(body.category !=null){assertValidCategory(body.category);
updates.category = body.category;}if(body.completed !=null){
updates.completed =Boolean(body.completed);}if(Object.keys(updates).length ===0){thrownewValidationError('no valid fields provided for update');}
updates.updatedAt =newDate();return updates;}
module.exports ={ALLOWED_CATEGORIES,
buildTaskDocument,
buildTaskUpdate,
ValidationError
};Altyapı:
Bu dosya, veritabanınıza girecek her şey için bekçilik yapar. Her oluşturma veya güncelleme isteği buradan geçer; böylece kuralları tek bir yerden uygularsınız.
Görevlerin daima doğru biçimde olmasını sağlar, kategorileri tutarlı kılar ve verilerinizi sorgulamaya başladığınızda sürprizleri önler. Özel ValidationError da hatalı girdileri gerçek sunucu sorunlarından temiz bir şekilde ayırmanıza olanak tanır; böylece API’niz uygun şekilde yanıt verebilir. Bu sayede uygulamanın geri kalanı basit kalır. Her rota, aldığı verinin zaten geçerli olduğunu bilerek kendi işine odaklanabilir. Sırada rotaları bağlayıp görevleri MongoDB’ye kaydetmeye başlamak var.
Adım 4: ObjectId ve İstek Doğrulamayı Yönetin
Artık bir görevin nasıl göründüğünü bildiğimize göre, bir göreve nasıl başvuracağımızı ele alalım. Bir rota bir :id parametresi içerdiğinde, bu değer düz bir metin olarak gelir. Ancak MongoDB bir ObjectId bekler. Bu dize yanlış biçimlendirilmişse, sürücü pek de yardımcı olmayan bir hata fırlatır. Bunu her rotada ele almak yerine, ara yazılımla merkezileştireceğiz; böylece her uç nokta aynı şekilde davranır.
Bunu yapmak için middleware/parseObjectId.js adlı bir dosya oluşturun ve aşağıdakileri ekleyin:
Bu ara yazılım, rota işleyicinizden önce çalışır. id’nin geçerli olup olmadığını kontrol eder, onu bir ObjectId’ye dönüştürür ve req.taskId üzerine ekler. Böylece rota mantığınız çalıştığında her zaman doğru bir ObjectId ile uğraşırsınız ve hatalı girdiler net bir 400 yanıtıyla erken reddedilir. Sırada bunu rotalarımıza takıp her şeyi birbirine bağlamak var.
Adım 5: Görev Rotalarını Oluşturun (CRUD + Filtreleme)
Bu noktada işin büyük kısmı zaten tamamlandı. Geçerli bir görevin nasıl görüneceğini tanımladık ve kimliklerin nasıl ayrıştırılıp doğrulanacağını ele aldık. Bu da rota işleyicilerimizin tek bir şeye odaklanabilmesi anlamına geliyor: veritabanıyla konuşmak.
Gerçek API rotalarını oluşturmak için routes/tasks.js adlı bir dosya oluşturun ve aşağıdakileri ekleyin:
const express =require('express');const{ getTasksCollection }=require('../db/connect');const{ALLOWED_CATEGORIES,
buildTaskDocument,
buildTaskUpdate,
ValidationError
}=require('../lib/taskDocument');const parseObjectId =require('../middleware/parseObjectId');const router = express.Router();// POST /tasks
router.post('/',async(req, res, next)=>{try{const doc =buildTaskDocument(req.body);const result =awaitgetTasksCollection().insertOne(doc);
res.status(201).json({_id: result.insertedId,...doc });}catch(err){if(err instanceofValidationError){return res.status(400).json({error: err.message });}next(err);}});// GET /tasks (optionally ?category=Work)
router.get('/',async(req, res, next)=>{try{const{ category }= req.query;if(category &&!ALLOWED_CATEGORIES.includes(category)){return res.status(400).json({error:`category must be one of: ${ALLOWED_CATEGORIES.join(', ')}`});}const filter = category ?{ category }:{};const tasks =awaitgetTasksCollection().find(filter).sort({createdAt:-1}).toArray();
res.json(tasks);}catch(err){next(err);}});// GET /tasks/:id
router.get('/:id', parseObjectId,async(req, res, next)=>{try{const task =awaitgetTasksCollection().findOne({_id: req.taskId });if(!task)return res.status(404).json({error:'task not found'});
res.json(task);}catch(err){next(err);}});// PUT /tasks/:id
router.put('/:id', parseObjectId,async(req, res, next)=>{try{const updates =buildTaskUpdate(req.body);const result =awaitgetTasksCollection().findOneAndUpdate({_id: req.taskId },{$set: updates },{returnDocument:'after'});if(!result)return res.status(404).json({error:'task not found'});
res.json(result);}catch(err){if(err instanceofValidationError){return res.status(400).json({error: err.message });}next(err);}});// DELETE /tasks/:id
router.delete('/:id', parseObjectId,async(req, res, next)=>{try{const result =awaitgetTasksCollection().deleteOne({_id: req.taskId });if(result.deletedCount ===0){return res.status(404).json({error:'task not found'});}
res.status(204).end();}catch(err){next(err);}});
module.exports = router;Altyapı:
Yukarıdaki her rota aynı deseni izler: istekten girdi alınır, daha önce yazdığımız yardımcılar üzerinden geçirilir, MongoDB çağrılır ve ardından bir yanıt döndürülür. Doğrulama ve kimlik ayrıştırma önceden halledildiği için buradaki kod küçük ve öngörülebilir kalır. Pratikte şöyle işler:
POST rotası, eklemeden önce buildTaskDocument ile yeni bir görev oluşturur
GET rotaları isteğe bağlı olarak kategoriye göre filtreler ve sonuçları en yeni başta olacak şekilde döndürür
PUT rotası buildTaskUpdate kullanır; böylece kısmi güncellemeler güvenli ve tutarlıdır
DELETE rotası bir görevi önceden ayrıştırılmış ObjectId’sine göre siler
Bu koddaki bazı ayrıntılara da değinmeye değer:
find() bir dizi değil, bir imleç döndürür; bu yüzden sonuçları gerçekten almak için sıralamadan sonra .toArray() zincirleriz
returnDocument: 'after' ile findOneAndUpdate size güncellenmiş belgeyi anında verir
Doğru HTTP durum kodlarını döndürüyoruz: oluşturma için 201, silme için 204 ve uygun yerlerde 400 veya 404
Beklenmeyen hatalar next(err) ile iletilir; böylece her rotanın içinde tek tek ele almak yerine tek bir yerde işlenebilirler
Adım 6: Sunucuda Her Şeyi Birbirine Bağlayın
Bu noktada tüm parçalar yerli yerinde. Doğrulama, temiz rotalar ve çalışan bir veritabanı bağlantımız var. Şimdi her şeyi birbirine bağlayıp sunucuyu gerçekten başlatmamız gerekiyor. Bir server.js dosyası oluşturun. Uygulamanın giriş noktası burası; her şey burada bir araya geliyor:
require('dotenv').config();const path =require('path');const express =require('express');const helmet =require('helmet');const{ connectDB, closeDB }=require('./db/connect');const tasksRouter =require('./routes/tasks');if(!process.env.MONGO_URI){
console.error('Fatal: MONGO_URI is not set. Copy .env.example to .env and fill it in.');
process.exit(1);}constPORT=Number(process.env.PORT)||3000;const app =express();
app.use(helmet());
app.use(express.json({limit:'100kb'}));
app.use(express.static(path.join(__dirname,'public')));
app.get('/health',(req, res)=>{
res.json({status:'ok',service:'task-manager-with-categories'});});
app.use('/tasks', tasksRouter);
app.use((req, res)=>{
res.status(404).json({error:'not found'});});
app.use((err, req, res, next)=>{if(err.type ==='entity.too.large'){return res.status(413).json({error:'payload too large'});}if(err.type ==='entity.parse.failed'){return res.status(400).json({error:'invalid JSON body'});}
console.error(err);
res.status(500).json({error:'internal server error'});});asyncfunctionstart(){try{awaitconnectDB();const server = app.listen(PORT,()=>{
console.log(`Server running on http://localhost:${PORT}`);});constshutdown=()=>{
console.log('\nShutting down gracefully...');
server.close(async()=>{awaitcloseDB();
process.exit(0);});setTimeout(()=> process.exit(1),10_000).unref();};
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);}catch(err){
console.error('Failed to start server:', err);
process.exit(1);}}if(require.main === module){start();}
module.exports ={ app, start };Altyapı:
Bu dosya her şeyi temiz bir şekilde birbirine bağlar. Her şey çalışmadan önce ortam değişkenlerinin ayarlı olduğunu kontrol eder, temel güvenliği helmet ile uygular ve uygulamanızın yanlışlıkla çok büyük yükleri kabul etmemesi için gelen JSON’u sınırlar. Ayrıca public/ klasörünüzü de sunar; böylece ön yüzünüz ayrı bir sunucuya ihtiyaç duymadan aynı uygulamada barınabilir.
Tüm rotalarınız /tasks altında bağlanır ve eşleşmeyen her şey temiz bir 404 döndürür. Hatalar her rotanın içinde tekrar edilmek yerine tek bir yerde ele alınır; bu da tutarlılığı ve bakımı kolaylaştırır. Veritabanı bağlantısı, sunucu dinlemeye başlamadan önce kurulur; böylece hazır olmadan istek kabul etmezsiniz ve kapatma işlemi zarifçe yapılır; bağlantılar düzgün şekilde kapatılır.
Şimdi npm run dev komutunu çalıştırarak her şeyin çalıştığından emin olalım. Her şey doğru şekilde bağlandıysa, aşağıdakine benzer bir çıktı görmelisiniz:
MongoDB connected (db: taskmanager)
Server running on http://localhost:3000
Altyapı:
Bu noktada her şey bağlandı ve çalışıyor! Artık tamamen çalışan bir API’miz var; hadi test edelim.
Adım 7: API Uç Noktalarını Test Edin
Bir UI oluşturmadan önce, her uç noktanın tek başına çalıştığını doğrulamak değerlidir. Bu, hata ayıklamayı çok daha kolay hale getirir; çünkü bir sorunun API’den mi yoksa ön yüzden mi kaynaklandığını hızla anlayabilirsiniz. Postman’i (veya herhangi bir HTTP istemcisini) kullanacağız ve birkaç isteği sırayla çalıştıracağız. Her biri, bir önceki adımda oluşturulan veriler üzerine inşa edilir.
Bir görev oluşturun: Başlık, açıklama ve kategori içeren bir POST isteği gönderin. Her şey yolundaysa, API yeni görevi ve oluşturulan _id ile birlikte 201 Created döndürür.
POST http://localhost:3000/tasks
Content-Type: application/json
{"title":"Write GeeksForGeeks article",
"description":"First draft by Friday",
"category":"Work"}Altyapı:
Birkaç tane daha oluşturun: Aynı POST isteğini farklı gövdelerle çalıştırın; böylece listeleme ve filtrelemeyi test etmek için yeterli veriye sahip olursunuz. Bundan sonra sorgulayabileceğiniz küçük bir veri kümeniz olacak.
{"title":"Go for a run","category":"Personal"}{"title":"Read MongoDB docs","category":"Study"}{"title":"Buy groceries"}// category defaults to "Other"Altyapı:
Tüm görevleri listeleyin: Bu, tüm görevleri en yeni başta olacak şekilde sıralı olarak döndürür.
Kategoriye göre filtreleyin: Bu yalnızca İş (Work) görevlerini döndürür. Daha önce eklediğiniz indeks, verileriniz büyüdükçe bu sorguyu hızlı tutar.
GET http://localhost:3000/tasks?category=WorkAltyapı:
Bir görevi güncelleyin: Az önce oluşturduğunuz görevlerden birinin _id değerini kullanın (POST yanıtından veya listeleme uç noktasından kopyalayabilirsiniz). Bu, güncellenmiş görevi döndürür. Yalnızca değiştirmek istediğiniz alanları göndermeniz yeterlidir; böylece kısmi güncellemeler basit kalır.
PUT http://localhost:3000/tasks/<paste-task-id-here>
Content-Type: application/json
{"completed":true}Altyapı:
Bir görevi silin: Silmek istediğiniz görevin _id değerini kullanın. Bu, 204 No Content döndürür. Aynı görevi tekrar getirmeye çalışırsanız, "task not found" ile birlikte bir 404 alırsınız.
Bu noktada, tüm rotaların beklendiği gibi davrandığını doğruladınız. API işini yapıyor; şimdi üzerine bir kullanıcı arayüzü inşa edebiliriz.
Adım 8: API ile Etkileşim Kurmak İçin Bir Ön Yüz Ekleyin
Postman, API’yi doğrulamak için harikadır; ancak bunun gerçek bir web sitesinde nasıl çalıştığını görmek de güzel olur. Bu yüzden uygulamamıza biraz ön yüz kodu ekleyelim.
Buraya büyük HTML ve CSS dosyaları yapıştırmak yerine, kodu bu GitHub deposuna ekledim. Oraya gidin ve proje kökünüzdeki public/ klasörünüze index.html, styles.css ve favicon.svg içeriğini kopyalayın. Değişiklikleri kaydedin ve ardından localhost:3000 adresine gidin. Ardından aşağıdaki kodu app.js dosyasına yapıştırın:
JavaScript kodu, her şeyi daha önce oluşturduğunuz API’ye tekrar bağlayan yerdir. Her işlev rotalarınızdan birine karşılık gelir; bu da ön yüzü takip etmeyi kolaylaştırır. Tüm istekler tek bir api() yardımcısından geçer; böylece hataları her yerde tekrarlamak yerine yalnızca bir yerde ele alırsınız.
Filtreleme, arka uca zaten eklediğiniz mantığa doğrudan bağlanan ?category=Work gibi bir sorgu dizesi geçirerek çalışır. Bir görevi oluşturduktan, güncelledikten veya sildikten sonra uygulama en son listeyi yeniden çeker; böylece arayüz senkron kalır. Görev sayacı küçük bir detaydır ama uygulamayı kullanırken daha canlı hissettirir.
Şimdi sunucunuzu npm run dev ile yeniden başlatın ve ardından http://localhost:3000 adresini açarak Görev Yöneticisi’ni deneyin. Birkaç görev ekleyin, güncelleyin, kategoriler arasında geçiş yapın ve bazılarını da silin. Bu noktada ön yüz ve arka uç uçtan uca tamamen bağlı ve birlikte çalışıyor.
Özet
Tebrikler! Node.js, Express ve MongoDB kullanarak sıfırdan eksiksiz bir görev yöneticisi API’si oluşturdunuz. Doğrulamayı ele aldınız, rotalarınızı küçük tuttunuz ve her şeyi çalışan bir sisteme bağladınız. Daha da önemlisi, MongoDB’yi soyutlamaların arkasına saklamak yerine onun gerçekte nasıl çalıştığına yakın kaldınız. Buradan itibaren uygulamayı üretime hazır hale getirmek için geliştirmeye devam edebilirsiniz.
Önemli Noktalar
Ara yazılım kullanmak ve doğrulamayı merkezileştirmek kodunuzu temiz ve öngörülebilir kılar.
Yerel MongoDB sürücüsü, gereksiz soyutlamalar olmadan kontrol sağlar.
İyi yapılandırılmış bir API, üretime hazır bir uygulamaya kolayca genişletilebilir.
SSS
MongoDB API’si Oluşturmak İçin Mongoose’a İhtiyacım Var mı?⌄
Hayır. Bu eğitim, daha fazla kontrol sağlayan ve yapıyı hafif tutan yerel MongoDB sürücüsünü kullanır. Şema soyutlamaları istiyorsanız Mongoose’u daha sonra ekleyebilirsiniz.
Şema Olmadan Verileri Nasıl Doğrularım?⌄
Doğrulamayı (örneğin buildTaskDocument gibi) yardımcı işlevlerde merkezileştirerek, veritabanına yazmadan önce her rotanın aynı kuralları uygulamasını sağlayabilirsiniz.
Geçersiz Bir ObjectId Gönderirsem Ne Olur?⌄
Ara yazılım, bunu erken aşamada bir 400 yanıtıyla reddeder ve MongoDB’nin kafa karıştıran hatalar fırlatmasını engeller.
Algoritmalar, sistem tasarımı ve davranışsal senaryoları kapsayan bu temel sorularla teknik mülakat sürecine hakim olun. Uzman cevapları, kod örnekleri ve kanıtlanmış hazırlık stratejileri edinin.
Git deponuzu temiz tutmak için .gitignore’u nasıl kullanacağınızı öğrenin. Bu eğitim; temelleri, yaygın kullanım durumlarını ve başlamanıza yardımcı olacak pratik örnekleri kapsar!
Düz bir yapılacaklar listesi başlangıçta gayet işe yarar: birkaç görev eklersiniz ve bitirdiğinizde kapatabilirsiniz. Ancak görevler artmaya devam ettikçe kullanımı zorlaşır. Bunu düzeltmenin bir yolu kategorilendirme eklemektir; böylece her görev İş, Çalışma, Kişisel vb. kategorilere ait olabilir ve listeyi ilgili kategoriye göre filtreleyebilirsiniz.
Bu eğitimde, Node.js ve MongoDB kullanarak görevleri kategoriye göre düzenlemenize olanak tanıyan bir REST API’nin nasıl oluşturulacağını öğreneceksiniz. Sonunda, oluşturma, listeleme, getirme, güncelleme ve silme olmak üzere beş uç noktanın yanı sıra bir kategori filtresine sahip çalışan bir Node.js sunucunuz olacak. Ayrıca, Mongoose gibi bir ODM’ye bağlı kalmadan yerel MongoDB sürücüsünü kullanarak kategori doğrulamasını nasıl zorunlu kılacağınızı da göreceksiniz.
Neler Öğreneceksiniz
Bu eğitimin tüm kodunu GitHub’da bulabilirsiniz; isterseniz klonlayıp eş zamanlı olarak okuyabilirsiniz.
Önkoşullar
Başlamadan önce şunlara sahip olmalısınız:
Adım 1: Projeyi Kurun
Basit görev yöneticimizi oluşturmaya başlayalım. İlk yapacağımız şey yeni bir klasör oluşturup terminalimizde aşağıdaki komutları çalıştırarak bir Node projesi başlatmak olacaktır:
Sırada, uygulamanın çalışması için gereken dört çalışma zamanı bağımlılığını kuracağız: yönlendirme için express, resmi MongoDB sürücüsü, ortam değişkenlerini yüklemek için dotenv ve son olarak güvenlik başlıkları için helmet.
Geliştirme için, sunucu dosyalar değiştiğinde otomatik olarak yeniden yüklensin diye aşağıdaki komutla nodemon’u geliştirici bağımlılığı olarak kurun:
Sonrasında, proje klasörünü IDE’nizde açın ve package.json içindeki scripts bölümünü aşağıdakileri içerecek şekilde güncelleyin. Bu sayede sunucuyu npm start ile başlatabilir veya geliştirme modunda npm run dev ile çalıştırabilirsiniz:
İlerledikçe oluşturacağımız yapı aşağıdaki gibi olacak. Her klasörün belirli bir rolü var: db/ veritabanı bağlantısını yönetir, lib/ yardımcı işlevleri içerir, middleware/ Express ara yazılımları içindir ve routes/ istek işleyicilerini tanımlar. public/ klasörü ise daha sonra ekleyeceğimiz küçük bir ön yüzü barındıracak. Küçük bir projede bile bu yapı işleri takip etmeyi kolaylaştırır ve yeni kodun nereye eklenmesi gerektiğini netleştirir. İsterseniz klasör yapısını şimdi oluşturup ilerledikçe doldurun ya da bunu atlayıp adımlar boyunca her dosyayı sırasıyla ekleyin:
Adım 2: MongoDB’ye Bağlanın
Görevleri depolamak ve almak için önce uygulamamızı MongoDB’ye bağlamamız gerekir. Bu, MongoDB sürücüsüne veritabanınıza nasıl bağlanacağını söyleyen bir bağlantı dizesi kullanılarak yapılır. Bunu doğrudan kodunuza gömmek yerine bir ortam dosyasında tutmak daha iyidir. Bu yaklaşım hassas değerleri kod tabanınızın dışında tutar ve ortamlar arasında geçişi kolaylaştırır.
Gerekli değişkenleri belgelemek için bir .env.example dosyası ve gerçek değerleriniz için bir .env dosyası oluşturun, şöyle:
Artık bir bağlantı dizesi hazır olduğuna göre, bunu MongoDB bağlantımızı kurmak için kullanabiliriz. Bir db/connect.js dosyası oluşturun. MongoDB istemcisini başlatacağımız ve uygulamanın geri kalanına erişilebilir kılacağımız yer burası olacak:
Bu dosya, uygulamanın geri kalanının yeniden kullanabileceği tek bir MongoDB istemcisi kurar. Bunu yalnızca bir kez oluşturmak istersiniz; çünkü sürücü zaten arka planda bağlantı havuzlamayı yönetir. Her istek için yeni bir istemci oluşturmak ilk başta sorun değilmiş gibi görünse de kısa sürede performans sorunlarına yol açar.
Adım 3: Görev Yapısını ve Doğrulamayı Tanımlayın
Bu noktada uygulama MongoDB’ye bağlanabiliyor. Şimdi, herhangi bir şey kaydetmeye başlamadan önce bir görevin gerçekte nasıl göründüğünü tanımlamamız gerekiyor.
Yerel MongoDB sürücüsünü kullanmak burada biraz farklı hissettirmeye başlar. Mongoose’daki gibi bir şema dosyası yok. Bunun yerine “şema”, veritabanına eklediğiniz nesnenin biçimidir. İlk başta biraz gevşek gelebilir, ancak aslında faydalıdır; çünkü MongoDB’nin gerçekte ne yaptığına yakın kalırsınız ve hiçbir şey soyutlamaların arkasına saklanmaz.
Yine de doğrulama istiyoruz. Bu nedenle bu mantığı farklı rotalara dağıtmak yerine tek bir yerde tutacağız; böylece her şey aynı kurallara uyar. lib/taskDocument.js adlı bir dosya oluşturun ve aşağıdaki kodu ekleyin:
Bu dosya, veritabanınıza girecek her şey için bekçilik yapar. Her oluşturma veya güncelleme isteği buradan geçer; böylece kuralları tek bir yerden uygularsınız.
Görevlerin daima doğru biçimde olmasını sağlar, kategorileri tutarlı kılar ve verilerinizi sorgulamaya başladığınızda sürprizleri önler. Özel ValidationError da hatalı girdileri gerçek sunucu sorunlarından temiz bir şekilde ayırmanıza olanak tanır; böylece API’niz uygun şekilde yanıt verebilir. Bu sayede uygulamanın geri kalanı basit kalır. Her rota, aldığı verinin zaten geçerli olduğunu bilerek kendi işine odaklanabilir. Sırada rotaları bağlayıp görevleri MongoDB’ye kaydetmeye başlamak var.
Adım 4: ObjectId ve İstek Doğrulamayı Yönetin
Artık bir görevin nasıl göründüğünü bildiğimize göre, bir göreve nasıl başvuracağımızı ele alalım. Bir rota bir :id parametresi içerdiğinde, bu değer düz bir metin olarak gelir. Ancak MongoDB bir ObjectId bekler. Bu dize yanlış biçimlendirilmişse, sürücü pek de yardımcı olmayan bir hata fırlatır. Bunu her rotada ele almak yerine, ara yazılımla merkezileştireceğiz; böylece her uç nokta aynı şekilde davranır.
Bunu yapmak için middleware/parseObjectId.js adlı bir dosya oluşturun ve aşağıdakileri ekleyin:
Bu ara yazılım, rota işleyicinizden önce çalışır. id’nin geçerli olup olmadığını kontrol eder, onu bir ObjectId’ye dönüştürür ve req.taskId üzerine ekler. Böylece rota mantığınız çalıştığında her zaman doğru bir ObjectId ile uğraşırsınız ve hatalı girdiler net bir 400 yanıtıyla erken reddedilir. Sırada bunu rotalarımıza takıp her şeyi birbirine bağlamak var.
Adım 5: Görev Rotalarını Oluşturun (CRUD + Filtreleme)
Bu noktada işin büyük kısmı zaten tamamlandı. Geçerli bir görevin nasıl görüneceğini tanımladık ve kimliklerin nasıl ayrıştırılıp doğrulanacağını ele aldık. Bu da rota işleyicilerimizin tek bir şeye odaklanabilmesi anlamına geliyor: veritabanıyla konuşmak.
Gerçek API rotalarını oluşturmak için routes/tasks.js adlı bir dosya oluşturun ve aşağıdakileri ekleyin:
Yukarıdaki her rota aynı deseni izler: istekten girdi alınır, daha önce yazdığımız yardımcılar üzerinden geçirilir, MongoDB çağrılır ve ardından bir yanıt döndürülür. Doğrulama ve kimlik ayrıştırma önceden halledildiği için buradaki kod küçük ve öngörülebilir kalır. Pratikte şöyle işler:
Bu koddaki bazı ayrıntılara da değinmeye değer:
Adım 6: Sunucuda Her Şeyi Birbirine Bağlayın
Bu noktada tüm parçalar yerli yerinde. Doğrulama, temiz rotalar ve çalışan bir veritabanı bağlantımız var. Şimdi her şeyi birbirine bağlayıp sunucuyu gerçekten başlatmamız gerekiyor. Bir server.js dosyası oluşturun. Uygulamanın giriş noktası burası; her şey burada bir araya geliyor:
Bu dosya her şeyi temiz bir şekilde birbirine bağlar. Her şey çalışmadan önce ortam değişkenlerinin ayarlı olduğunu kontrol eder, temel güvenliği helmet ile uygular ve uygulamanızın yanlışlıkla çok büyük yükleri kabul etmemesi için gelen JSON’u sınırlar. Ayrıca public/ klasörünüzü de sunar; böylece ön yüzünüz ayrı bir sunucuya ihtiyaç duymadan aynı uygulamada barınabilir.
Tüm rotalarınız /tasks altında bağlanır ve eşleşmeyen her şey temiz bir 404 döndürür. Hatalar her rotanın içinde tekrar edilmek yerine tek bir yerde ele alınır; bu da tutarlılığı ve bakımı kolaylaştırır. Veritabanı bağlantısı, sunucu dinlemeye başlamadan önce kurulur; böylece hazır olmadan istek kabul etmezsiniz ve kapatma işlemi zarifçe yapılır; bağlantılar düzgün şekilde kapatılır.
Şimdi npm run dev komutunu çalıştırarak her şeyin çalıştığından emin olalım. Her şey doğru şekilde bağlandıysa, aşağıdakine benzer bir çıktı görmelisiniz:
Bu noktada her şey bağlandı ve çalışıyor! Artık tamamen çalışan bir API’miz var; hadi test edelim.
Adım 7: API Uç Noktalarını Test Edin
Bir UI oluşturmadan önce, her uç noktanın tek başına çalıştığını doğrulamak değerlidir. Bu, hata ayıklamayı çok daha kolay hale getirir; çünkü bir sorunun API’den mi yoksa ön yüzden mi kaynaklandığını hızla anlayabilirsiniz. Postman’i (veya herhangi bir HTTP istemcisini) kullanacağız ve birkaç isteği sırayla çalıştıracağız. Her biri, bir önceki adımda oluşturulan veriler üzerine inşa edilir.
Bir görev oluşturun: Başlık, açıklama ve kategori içeren bir POST isteği gönderin. Her şey yolundaysa, API yeni görevi ve oluşturulan _id ile birlikte 201 Created döndürür.
Birkaç tane daha oluşturun: Aynı POST isteğini farklı gövdelerle çalıştırın; böylece listeleme ve filtrelemeyi test etmek için yeterli veriye sahip olursunuz. Bundan sonra sorgulayabileceğiniz küçük bir veri kümeniz olacak.
Tüm görevleri listeleyin: Bu, tüm görevleri en yeni başta olacak şekilde sıralı olarak döndürür.
Kategoriye göre filtreleyin: Bu yalnızca İş (Work) görevlerini döndürür. Daha önce eklediğiniz indeks, verileriniz büyüdükçe bu sorguyu hızlı tutar.
Bir görevi güncelleyin: Az önce oluşturduğunuz görevlerden birinin _id değerini kullanın (POST yanıtından veya listeleme uç noktasından kopyalayabilirsiniz). Bu, güncellenmiş görevi döndürür. Yalnızca değiştirmek istediğiniz alanları göndermeniz yeterlidir; böylece kısmi güncellemeler basit kalır.
Bir görevi silin: Silmek istediğiniz görevin _id değerini kullanın. Bu, 204 No Content döndürür. Aynı görevi tekrar getirmeye çalışırsanız, "task not found" ile birlikte bir 404 alırsınız.
Bu noktada, tüm rotaların beklendiği gibi davrandığını doğruladınız. API işini yapıyor; şimdi üzerine bir kullanıcı arayüzü inşa edebiliriz.
Adım 8: API ile Etkileşim Kurmak İçin Bir Ön Yüz Ekleyin
Postman, API’yi doğrulamak için harikadır; ancak bunun gerçek bir web sitesinde nasıl çalıştığını görmek de güzel olur. Bu yüzden uygulamamıza biraz ön yüz kodu ekleyelim.
Buraya büyük HTML ve CSS dosyaları yapıştırmak yerine, kodu bu GitHub deposuna ekledim. Oraya gidin ve proje kökünüzdeki public/ klasörünüze index.html, styles.css ve favicon.svg içeriğini kopyalayın. Değişiklikleri kaydedin ve ardından localhost:3000 adresine gidin. Ardından aşağıdaki kodu app.js dosyasına yapıştırın:
JavaScript kodu, her şeyi daha önce oluşturduğunuz API’ye tekrar bağlayan yerdir. Her işlev rotalarınızdan birine karşılık gelir; bu da ön yüzü takip etmeyi kolaylaştırır. Tüm istekler tek bir api() yardımcısından geçer; böylece hataları her yerde tekrarlamak yerine yalnızca bir yerde ele alırsınız.
Filtreleme, arka uca zaten eklediğiniz mantığa doğrudan bağlanan ?category=Work gibi bir sorgu dizesi geçirerek çalışır. Bir görevi oluşturduktan, güncelledikten veya sildikten sonra uygulama en son listeyi yeniden çeker; böylece arayüz senkron kalır. Görev sayacı küçük bir detaydır ama uygulamayı kullanırken daha canlı hissettirir.
Şimdi sunucunuzu npm run dev ile yeniden başlatın ve ardından http://localhost:3000 adresini açarak Görev Yöneticisi’ni deneyin. Birkaç görev ekleyin, güncelleyin, kategoriler arasında geçiş yapın ve bazılarını da silin. Bu noktada ön yüz ve arka uç uçtan uca tamamen bağlı ve birlikte çalışıyor.
Özet
Tebrikler! Node.js, Express ve MongoDB kullanarak sıfırdan eksiksiz bir görev yöneticisi API’si oluşturdunuz. Doğrulamayı ele aldınız, rotalarınızı küçük tuttunuz ve her şeyi çalışan bir sisteme bağladınız. Daha da önemlisi, MongoDB’yi soyutlamaların arkasına saklamak yerine onun gerçekte nasıl çalıştığına yakın kaldınız. Buradan itibaren uygulamayı üretime hazır hale getirmek için geliştirmeye devam edebilirsiniz.
Önemli Noktalar