in JavaScript, NodeJS, Programming, SQL

Yazı Öncesi Not: Öne Çıkan Görsel Knex.js Resmi Sitesinden Alınmıştır

Knex Nedir?

Knex kısacası PostreSQL, MSSQL, MySQL, MariaDB, SQLite3, Oracle ve Amazon Redshift için bir query builder olarak tanımlanabilir. ORM kullananlar zaten bu query builder nedir biliyorlar.

Sanıyorum ki Laravel Query Builder Knex’i bir hayli etkilemiş. Zaten Knex dokümanına da bakarsanız böyle olduğunu belirtmişler.

Geliştiricisi Tim Griesser, cypress isimli bir e2e framework projesinde mühendis olarak çalışmakta. Knex ile ilgili bu yazıyı okumaya başlamadan önce GitHub repository adresini vermek gerekiyor.

Repository: https://github.com/knex/knex

Knex Hangi Platformlarda Çalışıyor?

Knex en verimli şekilde Node.JS ile yani sunucu tarafında çalışmakta. Ancak isterseniz WebSQL limitleri dahilinde tarayıcı üzerinde de çalışabilmekte.

Sunucuda çalıştırılacak olan SQL query’lerinin tarayıcıda oluşturulmaması gerektiği konusuna değinmiyorum ancak kendileri yine de belirtmişler. Güvenlik açıklarına neden olabilir demiyorum, direkt olur yani.

Tarayıcı üzerinde çalışacağınız knex WebSQL ile ilgili basit işlemlerde çalışsa daha güzel olur gibi. Örneğin bir todo uygulaması.

Knex Güzel Ama TypeScript Desteği?

Evet, knex TypeScript ile uyumlu bir şekilde geliştirilmiş. Kendi söylemleri böyle. Knex kurulurken npm paketleri TypeScript bindingleri ile birlikte geliyormuş.

TypeScript için desteği olan IDE ve editörlerde bunu fark edeceksiniz.

Kurulum

Knex için iki aşamalı bir kurulum var. İlk kurulumda Knex’in kendisini kurmanız gerekiyor.

npm install knex --save

Bir sonraki aşamada ise hangi veri tabanı yöneticisini kullanacağınıza bağlı olarak kurulumlar gerçekleştirmelisiniz. Örneğin bu yazıda SQLite3 kullanacağız ama tamamını vereceğim.

npm install pg --save
npm install sqlite3 --save
npm install mysql --save
npm install mysql2 --save
npm install oracledb --save
npm install mssql --save

Bu kısımda kuruluma dair sorun yaşamayacaksınızdır. O zaman biraz kod yazalım. Ben bu örnek yazı için basit bir index.js dosyası oluşturuyorum.

İlk Ayarlamalar

İlk ayarlamalara gelelim. Farklı örnekleri sadece buraya bırakacağım, ben bu örnekte SQLite ile kullanımı göstereceğim.

MySQL Örneği

var knex = require('knex')({
  client: 'mysql',
  connection: {
    host : '127.0.0.1',
    user : 'DB_KULLANICINIZ',
    password : 'DB_PAROLANIZ',
    database : 'DATABASE_ADI'
  }
});

PostgreSQL Örneği

var knex = require('knex')({
  client: 'pg',
  version: '7.2',
  connection: {
    host : '127.0.0.1',
    user : 'DB_KULLANICINIZ',
    password : 'DB_PAROLANIZ',
    database : 'DATABASE_ADI'
  }
});

Tabii ki biz SQLite3 ile çalışacağımız için şöyle yapacağız;

SQLite3 Örneği

const knex = require('knex')

const db = knex({
  client: 'sqlite3',
  connection: {
    filename: "./DB_ADI.sqlite"
  }
});

Şu anda sadece knex’i projeye dahil ettik ancak herhangi bir şekilde knex için migration ya da schema generate etmedik. Bunun için 2 yöntem var. Birincisi tabii ki böyle direkt olarak dosya içerisinde bunu yapmak. Bir diğeri de npm run scripts olarak yapmak 🙂

package.json Dosyasını Düzenleyelim

scripts bölümü altına şunu ekleyelim;

"scripts": {
    "dev": "node index.js",
    "knex": "knex",
},

dev bizim dosya üzerinde iş yapabilmemiz için, direkt knex ise bir cli tool. Bence CLI tool ile ilerlemek daha faydalı. Öncelikle CLI tool ile nasıl oluyor onu görelim;

npm run knex

Bu komut ile ilgili komutları görebiliriz. Ben size şöyle vereyim;

Commands:
  init [options]                          Create a fresh knexfile.
  migrate:make [options] <name>           Create a named migration file.
  migrate:latest [options]                Run all migrations that have not yet been run.
  migrate:up [<name>]                     Run the next or the specified migration that has not yet been run.
  migrate:rollback [options]              Rollback the last batch of migrations performed.
  migrate:down [<name>]                   Undo the last or the specified migration that was already run.
  migrate:currentVersion                  View the current version for the migration.
  migrate:list|migrate:status             List all migrations files with status.
  seed:make [options] <name>              Create a named seed file.
  seed:run [options]                      Run seed files.

Öncelikle sunu demelisiniz;

npm run knex init

Bu dosya ile yeni bi knexfile oluşturuyor olacaksınız. Bu dosya knexfile.js adında olacak ve içeriği şunun gibi olacak;

// Update with your config settings.

module.exports = {

  development: {
    client: 'sqlite3',
    useNullAsDefault: true,
    connection: {
      filename: './dev.sqlite3'
    }
  },

  staging: {
    client: 'postgresql',
    connection: {
      database: 'my_db',
      user:     'username',
      password: 'password'
    },
    pool: {
      min: 2,
      max: 10
    },
    migrations: {
      tableName: 'knex_migrations'
    }
  },
}

Aşağıya kadar uzuyordu kısa kestim. Sonra bi migration olusturmamiz gerekiyor. Bakin bu migrationlari run etmek demiyorum, bir bakima database’in iskeletini. Şu komutunu kullanacağız;

npm run knex migrate:make todos

Yani todos isimli bir task bizi bekliyor. Bu task tablomuzu oluşturacak;

Oluşan migration dosyası migrations klasörü altında yer alacaktır. İçeriği boş olacak şekilde up ve down adında fonksiyonlar göreceksiniz. Örneğin;

exports.up = function(knex) {

};

exports.down = function(knex) {

};

Gelin bunları dolduralım; (Burada down üzerine örnek vermedim)

exports.up = function(knex) {
    return knex.schema.createTableIfNotExists('todos', function(table) {
        table.increments();
        table.string('title');
        table.text('description');
        table.dateTime('start_date');
        table.dateTime('due_date');
        table.timestamps();
    });
};

Şimdi son olarak şu komutla migrationlarimizi çalıştıralım;

npm run knex migrate:latest

Evet 🙂 Şu anda development environment’i kullanılarak bir database oluşturduk. İsterseniz dev.sqlite3 dosyasına bakabilirsiniz.

CRUD İşlemleri Nasıl Yapılıyor Peki?

Öncelikle bu knexfile’dan faydalanarak index.js dosyasını düzenleyelim;

const knex = require('knex');

const knexFile = require('./knexfile').development;

const db = knex(knexFile);

Artık development ortamı için knex configlerimiz hazır.

Insert İşlemi

Şimdi basit olarak insert işlemi yapan bir fonksiyon oluşturalım;

const insertData = (tableName, data) => {

    return db(tableName)
            .insert(data)
            .then(resp => resp)
            .finally(() => db.destroy());
}

Kısacası db instance’ı üzerinden insert işlemi yapacağız. Promise dönen bir yapıda olduğu için en sonda da db’yi destroy ediyoruz yoksa her defasında session açık kalıyor 🙂

Burada insertData fonksiyonu parametre olarak tablo adı ve data parametresi alıyor. Burada bulk insert yapacak isek array, değilse object passlayabiliriz.

insertData('todos', [
    {
        title: 'Knex yazısını yaz :)',
        description: 'Artık aşırı üşendiğin şu yazıyı yaz',
        start_date: '2020-01-01 12:00',
        due_date: '2020-02-15 16:56',
    }
])
.then(insertedId => {
    console.log(insertedId);
})

Eğer bulk insert var ise insertedId array, değilse normal value olarak dönecektir.

Select İşlemi

Basitçe şöyle bir select fonksiyonu yazalım.

const selectData = (tableName, options = { fields: [], filteringConditions: [] }) => {

    const { fields, filteringConditions } = options

    return db(tableName)
            .select(fields)
            .where(builder => {
                filteringConditions.forEach(condition => {
                    builder.where(...condition)
                });

            })
            .then(data => data)
            .finally(() => db.destroy());
}

Bu fonksiyonla kısacası aşağıdaki şekillerde çalışma yapabiliriz.

Herhangi bir where durumu içermeksizin bir sorgu;

selectData('todos')
.then(todos => {
    console.log(todos)
})

Diyelim ki bazı where sorgularımız olacak;

selectData('todos', {
    filteringConditions: [
        ['id', '!=', 37],
        ['description', 'LIKE', '%123%']
    ]
})
.then(todos => {
    console.log(todos)
})

Update İşlemi

Evet gelelim verinin nasıl güncelleneceğine dair bilgiye. Örneğin ben ID’si 38 olanın güncellenmesini istiyorum. Hadi bunun için de bi fonksiyon oluşturalım;

const updateData = (tableName, options = { fields: {}, filteringConditions: [] }) => {

    const { fields, filteringConditions } = options

    return db(tableName)
            .where(builder => {
                filteringConditions.forEach(condition => {
                    builder.where(...condition)
                });

            })
            .update(fields)
            .then(data => data)
            .finally(() => db.destroy());
}

Bunu şöyle kullanabiliriz;

updateData('todos', {
    fields: {
        title: 'Updated',
    },
    filteringConditions: [
        ['id', '=', 38]
    ]
})
.then(updateData => {
    console.log(updateData)
})

Tabii ki filteringConditions boş giderse ne olacağını söylemeye gerek yok 🙂

Delete İşlemi

Son işlem olarak Delete’i göreceğiz. Buna da bir fonksiyon yazalım.

const deleteData = (tableName, options = { filteringConditions: [] }) => {

    const { filteringConditions } = options

    return db(tableName)
            .where(builder => {
                filteringConditions.forEach(condition => {
                    builder.where(...condition)
                });

            })
            .del()
            .then(data => data)
            .finally(() => db.destroy());
}

Ve kullanımı da şöyle olmalı 🙂

deleteData('todos', {
    filteringConditions: [
        ['id', '=', 38]
    ]
})
.then(deleteData => {
    console.log(deleteData)
})

Yine burada filteringConditions boş geçilirse bütün datayı siler. Tüm SQL olanaklarını burada açıklamayacağım. Ama kısaca bir CRUD için temel yapıyı hazırladık 🙂

Dosya Tabanlı Migration Oluşturma

Bence CLI tool varken buna çok gerek yok ama öncelikle database için şemayı şöyle oluşturalım.

db.schema.createTable('todos', (table) => {
    table.increments();
    table.string('title');
    table.text('description');
    table.dateTime('start_date');
    table.dateTime('due_date');
    table.timestamps();
});

Daha sonra hazırda bekletilmek adına migrationlarımızı oluşturalım.

db.migrate.make('todos');

Son olarak da çalıştırılmamış olan tüm migrationları çalıştırır.

db.migrate.latest();

Bu işlemden sonra CRUD işlemleri aynı şekilde yapılacaktır. Bunu tekrar anlatmaya gerek yok diye düşünüyorum 🙂

Kaynaklar

Bu yazıyı hazırlarken aşağıdaki kaynaklardan faydalandım;