Blog

Express.js & MongoDB ile Pooling Application

Express Nedir?

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.

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.

"dependencies": {
     "express": "^4.17.1"
}

Mongoose Nedir?

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

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.

Pool Application (PoolApp) Nedir?

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 poollapp-xxx veri tabanına bağlanarak yanıt alacaktır.

Nasıl?

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.

{
  "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 Nedir?

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.

Body Parser Nedir?

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.

Main Models Nedir?

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 model de 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.

İndex.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çim export bloğumuza bir parametre geçeceğiz ve bu parametreyi buraya yolladığımızda modellerimiz belirttiğimiz koşullarla otomatik olarak oluşacak. Bunu yapabilmek için aşağıda ki kod bloğunu index.js dosyası içerisine yazmamız yeterli olacaktır.

Ş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',
  },
  private: {
    secret: '.]:x1{$A^Ts_m%iXJ$p^{;yUBT8uPJ<.`med:t/T`K=8l%d#!D0m9lxmkK4#_L*',
    salt: '.NHjN2r1P`*qst_ucBG2',
  },
  cloud: {
    secret: 'b1DVHL1,mZ%>6:]#f<HJ6~l(GhN?dHMx>/w2t$"}LP#b<bN|5"sDE*-p|S&BtpJ',
    salt: 'aJ",^}A*r+s.CPc$[V:(0',
  },
  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}));

app.use((err, req, res, next) => {
  if (err) {
    // app.get('log').error({
    //   message: err.toString(),
    //   service: 'root',
    // });
    return res.status(400).json({
      type: false,
      data: err.toString(),
    });
  }
  next();
});

// NOTE: Cors Problemleri bu kod bloğuyla çözülebilir.
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. Uygulamamıza gelen herhangi bir request yukarıda yazdığımız middleware kontrollerinden geçecektir. Bununla birlikte yukarıda “app.use(‘/’, api)”  kod bloğu kafanızı karıştırmasın, app ifadesi daha önce express çatısını import ettiğimiz yerde bir obje olarak oluşturuldu.

const express = require('express');

const app = express();

app.use uygulamaya yapılan her istek için genel dizinde bulunan api dosyasının yürütülmesini sağlayacak. Şimdi genel dizinimizde api klasörü oluşturalım ve api klasörünü main ve sub olmak üzere iki yola ayıralım. Bu aşamalardan sonra ana veri tabanımıza ilk create işlemimizi yapabilmek için metodumuzu ekleyelim.

Ana dizinimizde tüm klasörlerimizde olduğu gibi index.js dosyası bulanacak.

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

// NOTE: Özel erişim kontrolü
app.use((req, res, next) => {
  // NOTE: 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;

Kod bloğumuzda ana veri tabanına gelecek olan erişim izni olmayan sub domainlerin engellenmesi için bir kontrol yazdık. index.js dosyasının gerisi ise tüm index.js lerde bulunan ortak kod bloğuna sahiptir.

Sub klasörümüz için gerekli olan index.js dosyasını yazmadan önce yaratılacak olan veri tabanlarının genel model yapısını oluşturalım. Ana dizinde sub_models klasörü yaratmamız gerekiyor.

Users modelimiz, kullanıcı isimlerini bulunduracak ve bu alan zorunlu olacak. Bununla birlikte yeni bir veri tabanı oluşturulduğunda veri tabanı ismi burada ki users tablosunda ki name alanına yazılacak.

const {Schema} = require('mongoose');

module.exports = new Schema({
  name: {type: String, trim: true, required: true}
}, {collection: 'users'});

index.js dosyamız, main klasörümüzün içinde modeller için oluşturduğumuz index.js dosyası ile aynı özelliklere sahip 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',
    });
  }
  if (req.originalUrl === '/xd') {
    return next();
  }

Bu kod bloğunda ise istek yapılan veri tabanının gerçekten var olup olmadığı kontrol edilmiştir.

req.data = {
    prefix: req.hostname.split('.')[0],
    db: null,
  },
    // var olan db listesinde gelinen hesabın olup olmadığını kontrol eder.
    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ı',
        });
      }

index.js te devam eden kod bloğunda modellerimizle olan bağlantımızı sağlayacağız. Daha önce sub_models klasörü içine “db” parametresini yazmıştık. Bu parametreyi index.js te bulunan metodumuza yollayarak bağlantımızı sağlayacağız.

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

Bundan sonra yapmamız gereken, tüm index.js dosyalarında bulunan ortak kod bloğunu eklemektir.

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;

Artık app.js içerisinde app.use(‘/’, api) kod bloğuyla kullanılan ve genel dizinde bulunan api klasörü içerisinde bulanan index.js dosyasını yazabiliriz.

const express = require('express');
const app = express();
const path = require('path');
const basename = path.basename(__filename);

// Sub ve Main routelar ın yönetildiği ortak middleware
const main = require('./main/index');
const sub = require('./sub/index');

app.use('/main', main);
app.use('/sub', sub);

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;

Genel route yapımızı yöneten index.js doyasını app.js dosyası içinde import edip express ile birlikte kullanmamız yeterli olacaktır.

Şimdi veri tabanlarımızı mongoose içerisinde bulunan create metoduyla oluşturup oluşturduğumuz veri tabanlarının isimlerini ana veri tabanımızda bulanan business modelimize ve oluşan veri tabanımız içerisinde ki users tablosuna kaydedelim.

Bunun için main klasörümüz içerisinde bulunan register.js dosyamızı kullanacağız.

const express = require('express');
const app = express();
const subModels = require('../../sub_models/index');
const mongoose = require('mongoose');
const config = require('../../bin/config');

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

module.exports = app;

Görüldüğü üzere create metodu ana veri tabanımız içinde bulunan business modelimize ve oluşturulan veri tabanındaki users modeline kullandığımız ismi kaydetmektedir. Bunu yaparken daha önce belirttiğimiz üzere, MongoDB içinde modeli ve veri tabanını daha önceden tanımlamamıza ihtiyaç yoktur. Connection kurulduktan sonra kaydedilen ilk verilerle birlikte veri tabanı ve model bizim için oluşacaktır.

Veri tabanlarına ayrı ayrı kaydedilen ve oluşturulan her collection için farklı olan kayıtları görebileceğimiz basit bir get metodunu sub klasörü içinde bulunan users.js dosyasına ekleyelim.

const express = require('express');
const app = express();
const path = require('path');
const basename = path.basename(__filename);

app.get('/', (req, res) => {
  req.data.db.model('users').find({}, (err, data) => {
    return res.json({
      type: true,
      data,
    });
  })
});

module.exports = app;

Artık projemizi npm start ile başlatıp postman üzerinden test edebiliriz. Eğer kaydettiğimiz tüm veri tabanlarının ismini görmek istiyorsanız business dosyasını içerisine aşağıda ki gibi bir get metodu yazabilirsiniz.

app.get('/', (req, res) => {
  app.get('db').model('business').find({}, (err, data) => {
      if (err) {
        return res.json({
          type: false,
          message: err.toString(),
        });
      }
      return res.json({
        type: true,
        data,
      });
    }
  );
});

Servisimizi Postman ile Test Edelim

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 ornekbir prefixi 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.

Benzer
Bloglar

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.