Express.js ve MongoDB ile connection pooling, birden fazla veri tabanına tek bir Node.js uygulaması üzerinden dinamik bağlantı sağlamayı mümkün kılan bir mimarik yaklaşımdır. Multi-tenant SaaS uygulamalarında her müşterinin ayrı bir MongoDB veri tabanına sahip olduğu senaryolarda, her istek için yeni bağlantı açmak yerine subdomain prefix’ine göre ilgili veri tabanına yönlendirme yapılması hem performans hem de veri izolasyonu açısından kritik önem taşır. Bu yazıda Express.js, Mongoose ve MongoDB kullanarak bu yapıyı sıfırdan nasıl inşa edeceğinizi adım adım ele alıyoruz. Node.js ile API testi konusunda daha fazla bilgi edinmek isteyenler için ek kaynaklar yazı sonunda yer almaktadır.
İçindekiler
Express, Node.js içerisinde web uygulamalarının daha kolay ve hızlı bir şekilde geliştirilmesini sağlamak amacıyla yazılmış bir framework’tür.
Express ile yeni bir uygulama başlatmak için yapılması gereken çok kolay birkaç adım bulunmaktadır.
İlk önce terminal ekranında çalışılmak istenen dizine gidip npm init komutuyla yeni bir Node.js projesi başlatmanız gerekmektedir.
TERMINAL
mkdir poolapp cd poolapp npm init
Proje başlatma adımları tamamlandıktan sonra yapmanız gereken Node Package Manager’ı kullanarak Express çatısını projenize yüklemektedir. Npm hakkında bilgi vermek gerekirse temel olarak 3.parti yazılımları yüklemeyi sağlayan bir araçtır.
npm install express --save
Bu komut express çatısını poolapp ismini verdiğimiz uygulamanın dependencies listesine ekleyecektir. İşlem başarılı olduğunda package.json dosyası içinde framework adı ve versiyon numarası gözükecektir.
JSON
"dependencies": { "express": "^4.17.1" }
Mongoose bir ODM modülüdür. ODM ise object data modeling olarak tanımlanmaktadır. MongoDB ile veriler karmaşık bir şekilde kaydedilebilir. Bu durum veriler üzerinde yapılacak olan işlemleri zorlaştırır. Bu işlemleri kolaylaştırmak için mongoose kullanılır. Mongoose ile birlikte belirli bir model yapısı ve bu modele ait olan herhangi bir kaydın nasıl kısıtlamalara sahip olabileceğini belirleyebiliriz. Aynı zamanda mongoose CRUD işlemlerinin daha kolay bir şekilde yapılabilmesini de sağlar.
npm install mongoose --save
Yukarıda bulunan komut ile birlikte Mongoose projemize eklenebilir. Daha sonra kendinize bir schema yaratabilirsiniz. Schema sizin mongoDB üzerinde oluşturacağınız modelin içerisindeki alanların yapısını ve kısıtlamaları belirtecektir.
JAVASCRIPT
const {Schema} = require('mongoose'); module.exports = new Schema({ name: {type: String, trim: true, required: true} }, {collection: 'users'});
Örnek olarak yukarıda ki gibi bir user modeli oluşturulabilir ve users modelinde ki name alanı required komutu ile zorunlu alan olarak belirlenebilir. Eğer, users modeline name parametresi olmadan bir create işlemi yapılacak olursa mongoose bunu engelleyecek ve alanın zorunlu olduğu uyarısını gösterecektir.
PoolApp birden fazla DB ile tek uygulama üzerinden bağlantı kurabilen bir Node.js projesidir. Daha açık ifade etmek gerekirse, bu proje ile ilk önce ana bir veri tabanı bağlantısı kuracağız. Bundan sonra, ana veri tabanı içerisinde oluşturulan diğer veri tabanlarının adlarını kaydedip oluşturulan veri tabanlarına gelen requestteki prefix alanlarıyla bağlantı sağlayacağız. Yani localhost üzerinde çalıştığımızda yyy.localhost:3000/api üzerine gelen herhangi bir request poolapp-yyy veri tabanına, xxx.localhost:3000/api ise poolapp-xxx veri tabanına bağlanarak yanıt alacaktır.
yyy.localhost:3000/api
xxx.localhost:3000/api
Bunun için ilk önce genel dizinimizde bir app.js dosyası oluşturacağız; app.js bizim ana veri tabanımızla bağlantımızı kuracağımız dosya olacaktır. Ancak, uygulamamıza start verdiğimizde app.js üzerinden başlamasını söylememiz gerekiyor. Package.json dosyası üzerinde yapılacak olan değişiklikler bunu sağlayacaktır.
app.js
{ "name": "poolapp", "version": "1.0.0", "description": "Pool application", "main": "app.js", "author": "", "license": "ISC", "dependencies": { "body-parser": "^1.19.0", "express": "^4.17.1", "mongoose": "^5.9.17" }, "devDependencies": { "nodemon": "^2.0.4" }, "scripts": { "test": "echo "Error: no test specified" && exit 1", "start": "nodemon app.js" } }
Yukarıda ki kod bloğunda görüldüğü üzere main alanı “app.js” olarak belirlenmiş. Ancak scripts alanının altında start için “nodemon app.js” yazılmıştır.
Nodemon, node js ile geliştirilen bir uygulamada anlık olarak yaptığınız değişikleri sunucuyu otomatik olarak yeniden başlatarak gösteren bir modüldür. Yani herhangi bir dosyada yapacağınız ctrl+s işleminden sonra tekrar npm start yazmanıza gerek kalmamasını sağlayarak, Node.js ile uygulama yazarken bize zaman kazandırmaktadır.
Artık app.js dosyamızı express ile yazmaya başlayabiliriz.
const express = require('express'); const app = express(); const server = require('http').createServer(app); const process = require('process'); const config = require('./bin/config'); const mainModels = require('./main_models/index'); const api = require('./api/index'); const mongoose = require('mongoose'); const bodyParser = require('body-parser');
İlk kod bloğumuz yukarıdaki gibi olacak. Burada henüz oluşturulmayan dosyalardan gelen özellikler de bulunmakta; bunları “app.js” kod bloğunu oluşturmadan önce birlikte oluşturup neye hizmet ettiğini göreceğiz.
Görüldüğü üzere body-parser 3. parti modülü app.js içerisinde kullanılmış. Body-parser gönderilen herhangi bir POST isteği içerisinde ki verileri obje olarak yakalamamızı sağlayacaktır. Yine “npm install body-parser” komutuyla bu modülü yükleyebiliriz.
Ana veri tabanımız üzerinde oluşturacak olduğumuz veri tabanlarının isimlerini tutacağız. Bunu yapmamızın sebebi ise, eğer bir veri tabanıyla bağlantı kurulmak isteniyorsa ve bu veri tabanı ismi ana veri tabanı içerisinde ki modelde tutulan herhangi bir isimle eşleşmiyorsa bu durumu isteğe olan cevabımızda belirteceğiz. İşte bu yüzden ana veri tabanında bulunan, modellerimizi tutacağımız bir klasör oluşturacağız ve bu klasör içerisinde mongoose ile schema sınıfı kullanarak uygun modellemeyi yapacağız. Bu klasörün ismi poolapp projesinde “main_models” olarak belirlenmiştir.
business.js adı verilen bir dosya oluşturarak yaratılan veri tabanlarının isimlerinin hangi alanla ve nasıl tutulması gerektiğini belirteceğiz.
const {Schema} = require('mongoose'); module.exports = new Schema({ prefix: {type: String, trim: true, required: true}, }, {collection: 'business'});
Business modeli içerinde ki prefix alanının schema sınıfı ile birlikte zorunlu alan olduğunu ve tipinin String olması gerektiğini belirttik.
Bunun dışında bir de “Schema”nın bulunduğu klasörümüzde bir de index.js dosyası bulunmaktadır.
Index.js bize mongoose ile veri tabanına connection kurulduğunda, bu klasör içerinde bulunan tüm model yapılarının kurulmasını sağlayarak hizmet edecek. Bunu başarabilmek için export bloğumuza bir parametre geçeceğiz ve bu parametreyi buraya yolladığımızda modellerimiz belirlediğimiz koşullarla otomatik olarak oluşacak.
Şimdi index.js dosyasını kullanarak ana veri tabanımızı app.js üzerinden nasıl oluşturabileceğimizi görelim.
const path = require('path'); const basename = path.basename(__filename); module.exports = (db) => { require('fs') .readdirSync(__dirname) .filter((file) => { return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); }).forEach((file) => { filename = file.split('.')[0]; key = filename.split('_').map( (name) => filename.split('_')[0] !== name ? name.charAt(0).toUpperCase() + name.slice(1) : name, ); db.model(key, require(__dirname + path.sep + file)); }); return db; };
MongoDB ilişkisel veri tabanlarında olduğu gibi daha önceden tabloların oluşturulmasına gerek duymaz. Mongoose içerisinde ki “connection” metodu ve hazırladığımız index.js dosyası ile bu işlemi kolayca yapabiliriz. Ancak tüm bu işlemleri yapabilmek için ana dizinimizde “bin” adını vereceğimiz bir klasör içerisinde konfigürasyon dosyası oluşturarak veri tabanına ait host, isim ve port gibi bilgilerimizi global olarak tanımlayacağımız bir config.js dosyası oluşturacağız.
const path = require('path'); const APP_ROOT = path.dirname(require.main.filename); const SEP = path.sep; module.exports = { db: { host: '127.0.0.1', name: 'pool_app_cloud', port: '27017', prefix: 'poolapp', }, server: { port: 3000, ip: '127.0.0.1', }, };
Artık “app.js” dosyası üzerinde değişiklik yapabiliriz. Bu dosya üzerinde ana veri tabanı bağlantımızı sağlayacağız ve modellerimiz yeni kayıt oluşturulduğunda otomatik olarak veri tabanımıza eklenecek.
mongoose.createConnection( `mongodb://${config.db.host}:${config.db.port}/${config.db.name}`, {useNewUrlParser: true}, ).then((connection) => { console.log('new connection'); app.set('db', mainModels(connection)); app.set('db_admin', connection.db.admin()); }).catch((err) => { }) app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); // CORS ayarları app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header( 'Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization', ); if (req.method === 'OPTIONS') { res.header('Access-Control-Allow-Methods', 'PUT, POST, PATCH, DELETE, GET'); return res.status(200).json({}); } next(); }); app.use('/', api); server.listen(config.server.port, config.server.ip);
App.js dosyamız artık config dosyamızda belirttiğimiz sunucu portu ve sunucu ip’sini dinliyor. Uygulamaya gelen herhangi bir request yukarıda yazdığımız middleware kontrollerinden geçecektir.
Şimdi genel dizinimizde api klasörü oluşturalım ve api klasörünü main ve sub olmak üzere iki yola ayıralım.
İlk önce main ve sub klasörlerimizi için index.js dosyalarımızı, kontrollerle birlikte oluşturalım. Main klasörü içerisinde ki index.js’den başlamak işimizi kolaylaştıracaktır.
const express = require('express'); const app = express(); const path = require('path'); const basename = path.basename(__filename); // Özel erişim kontrolü app.use((req, res, next) => { // Subdomain engelle if (req.hostname.split('.').length !== 2 && req.hostname !== 'localhost' ) { return res.json({ type: false, data: 'Erişiminiz engellendi.', }); } return next(); }); app.get('/', (req, res) => { return res.json({ type: true, data: true, }); }); require('fs') .readdirSync(__dirname) .filter((file) => { return ( file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js' ); }).forEach((file) => { app.use(`/${file.split('.')[0]}`, require(__dirname + path.sep + file)); }); module.exports = app;
Users modelimiz, kullanıcı isimlerini bulunduracak ve bu alan zorunlu olacak.
Sub klasörü içerisinde ki index.js dosyasında ilk önce prefix olmadan gelen istekler engellenmelidir:
app.use((req, res, next) => { if (req.hostname.split('.').length !== 3 && req.hostname.split('.')[1]!=='localhost') { return res.json({ type: true, message: 'Erişiminiz engellendi', }); } // İstek yapılan veri tabanının var olup olmadığını kontrol et req.data = { prefix: req.hostname.split('.')[0], db: null, }; app.get('db_admin').listDatabases((err, dbsResult) => { if (err) { return res.json({ type: false, message: err.toString() }); } if (!dbsResult.databases.some((e) => e.name === `${config.db.prefix}_${req.data.prefix}`)) { return res.json({ type: false, data: 'Hesap Bulunamadı' }); } // Veri tabanına bağlan mongoose.connect( `mongodb://${config.db.host}:${config.db.port}/${config.db.prefix}_${req.data.prefix}`, { useNewUrlParser: true }, ).then((connection) => { req.data.db = subModels(connection); return next(); }).catch((err) => { return res.json({ type: false, message: err.toString() }); }); }); });
app.post('/', (req, res) => { app.get('db').model('business').create( { prefix: req.body.name }, (err) => { if (err) return res.json({ type: false, message: err.toString() }); mongoose.connect(`mongodb://${config.db.host}:${config.db.port}/poolapp_${req.body.name}`) .then((connection) => { const dbResult = subModels(connection); dbResult.model('users').create({name: req.body.name}, (err, data) => { if (err) return res.json({ type: false, message: err.toString() }); return res.json({ type: true, message: 'Şirket oluşturulmuştur' }); }); }); } ); });
Register metodu ile yeni bir veri tabanı oluşturmak:
GET isteği ile oluşturulan veri tabanı listesinin alınması:
Gerçekleştirdiğimiz GET isteğiyle oluşturduğumuz veri tabanı listesini görüyoruz. Görüldüğü üzere ilk kaydımızla birlikte modelimiz oluştu ve kayıt için otomatik olarak bir id atandı.
Şimdi ise örnek bir prefix kullanılarak users modelinin içine ana veri tabanıyla aynı anda kaydedilen ismi görüyoruz. Aynı örneği istediğiniz bir prefix ismiyle deneyebilirsiniz.
Bu mimariyi SAP ile entegre uygulamalarda da kullanabilirsiniz. CAP projelerinde JavaScript mi TypeScript mi tercih edilmeli? sorusu da benzer bir mimari kararı gerektirmektedir — her ikisi de okunabilirliği ve bakım kolaylığını doğrudan etkiler.
Express.js MongoDB connection pooling neden önemlidir?
Her HTTP isteğinde yeni bir MongoDB bağlantısı açmak hem yavaş hem de kaynak yoğun bir işlemdir. Connection pooling ile mevcut bağlantılar yeniden kullanılır; bu durum özellikle yüksek trafikli uygulamalarda yanıt süresini ve sunucu yükünü önemli ölçüde azaltır. Multi-tenant uygulamalarda ise her tenant için ayrı bağlantı havuzu yönetimi veri izolasyonunu da garanti eder.
Mongoose’un createConnection ve connect metotları arasındaki fark nedir?
mongoose.connect() varsayılan bağlantıyı oluşturur ve tek bir MongoDB instance’ına bağlanır. mongoose.createConnection() ise birden fazla bağımsız bağlantı oluşturmanıza olanak tanır; her bağlantı kendi model havuzuna sahip olur. Multi-tenant mimarisinde her tenant için ayrı bağlantı yönetmek amacıyla createConnection tercih edilmelidir.
Bu mimari production ortamı için uygun mu?
Bu yöntem iyi bir temel oluşturmaktadır; ancak production’a geçmeden önce bağlantı havuzu boyutlandırması (poolSize parametresi), bağlantı zaman aşımı yönetimi, bağlantı sağlık kontrolü (heartbeat), hata durumunda yeniden bağlanma mantığı ve bellek yönetimi gibi konuların dikkate alınması önerilir.
Subdomain tabanlı tenant yönlendirmesi nasıl çalışır?
Gelen HTTP isteğinin hostname’inden subdomain prefix’i ayrıştırılır (örn. tenant1.localhost:3000 → prefix: tenant1). Bu prefix, ana veri tabanındaki business koleksiyonuyla eşleştirilir; eşleşme varsa ilgili MongoDB veri tabanına (poolapp_tenant1) dinamik olarak bağlanılır. Eşleşme yoksa istek reddedilir.
tenant1.localhost:3000
tenant1
poolapp_tenant1
Mongoose Connection Docs — mongoosejs.com Express.js Routing Guide — expressjs.com Node.js ile API Testi Nasıl Yapılır? — MDP Group CAP Projelerinde JavaScript mi TypeScript mi? — MDP Group
Yazılım Geliştiricisi
SAP GTS İşletmelere Ne Sunuyor?
Küresel ticaret, oldukça karmaşık bir süreçtir. Bu yüzden düzenleyici, her sipariş için doğru olanı uygulanması ve sevkiyatı gibi tüm...
Fintech Çözümlerinin Önemi ve SAP Finans Dijitalleşmesi
Fintech çözümleri, finansal hizmetleri teknoloji aracılığıyla daha hızlı, düşük maliyetli ve erişilebilir kılan uygulama ve sistemlerin...
Şirketler için BT Varlık Yönetiminin Önemi
BT varlıkları, şirketlerin bel kemiğini oluşturmakta ve şirkete ait operasyonların alt yapısı için hayati önem taşımaktadır....
Ağır Sanayide Lojistik Optimizasyonu: EWM ile ASR Entegrasyonu
Ağır sanayi sektöründe lojistik yönetimi, sıradan bir yükleme-boşaltma sürecinin çok daha ötesindedir. Yüksek hacimli, ağır ve çoğu...
Azure Logic Apps ile Entegrasyon Rehberi
Günümüzde işletmeler, onlarca farklı uygulama, SaaS servisi, ERP sistemi ve veri tabanı arasında sürekli veri alışverişi yapmak zorunda....
React-Native Animasyon İşlemleri Nasıl Yapılır?
Mobil uygulamalarda animasyonların kullanılması, uygulamanın hissiyatını büyük oranda etkilemektedir. Kullanıcılar hızlı ve akışkan...
Hatalı Düzenlenen e-Fatura Nasıl Düzeltilir? 2026 Rehberi
Hatalı düzenlenen e-faturanın nasıl düzeltileceği, faturanın senaryosuna göre değişiklik göstermektedir. Ticari faturalarda alıcı, GİB...
Tedarik Zinciri Yönetiminde Depolamanın Rolü
Tedarik zinciri yönetiminin amacı, malların ve hizmetlerin kaynak noktasından tüketim noktasına kadar olan akışını kesintisiz bir şekilde...
SAP Data Hub Nedir? Avantajları Nelerdir? Kapsamlı Rehber
SAP Data Hub, 27 Eylül 2017 tarihinde yayınlanan; şirketlerin, çeşitli veri ortamlarında veri akışını hızlandırmasına ve genişletmesine...
Mailiniz başarıyla gönderilmiştir en kısa sürede sizinle iletişime geçilecektir.
Mesajınız ulaştırılamadı! Lütfen daha sonra tekrar deneyin.