TypeORM Nedir? Typescript ile MySQL Bağlantısı ve CRUD İşlemleri

Burcu S
8 min readDec 7, 2020

--

TypeORM hakkındaki bu yazıyı okumadan önce eğer ORM(Object Relational Mapping) hakkında hiçbir fikriniz yoksa anlatılanların havada kalmaması adına ORM’un ne olduğuyla ilgili küçük bir araştırma yapmanızı öneririm. Ben de çok kısa şekilde ORM’dan bahsederek konuya giriş yapacağım.

ORM (Object Relational Mapping) Nedir?

Literatürde veritabanında herhangi bir işlem yapmak için SQL dilini kullanırız ve bazen basit işlemler için dahi uzun SQL sorguları yazmak zorunda kalırız. İşte ORM teknolojisi herhangi bir platform ile çalışırken yapacağımız veritabanı işlemlerini SQL’den neredeyse bağımsız kılar. ORM/nesne ilişkisel haritalamanın mantığı basitçe şudur: Veritabanındaki her bir yapıyı, projeyi geliştirirken kullandığımız nesne yönelimli programlama dili ile temsil edecek nesneler, fonksiyonlar oluştururuz ve işlem gerçekleştirirken de bu nesne ve fonksiyonları kullanırız.

ORM sayesinde SQL yerine daha basit ifadelerle işlem yapabileceğimizden bahsetmiştim.

Örneğin veritabanına bir nesne ekleme işlemi:

SQL ile:

INSERT INTO Users (Name, Lastname, Age, City) VALUES ('John','Doe',23,'Any');

ORM ile:

User newUser = new User('John','Doe',23,'Any');
newUser.save();

ifadeleri ile gerçekleştirilir. Özetle ORM teknolojisi basit işlemler için bizi uzun SQL sorguları yazmaktan kurtarmaktadır.

Asıl konumuz TypeORM olduğundan yazıya bu konu ile devam etmek istiyorum.

TypeORM Nedir?

TypeORM, tarayıcılar, NodeJS, React Native gibi platformlarda çalışabilen ve TypeScript, JavaScript ile kullanılabilen bir ORM teknolojisidir. Active Record ve Data Mapper olmak üzere iki farklı yaklaşımı vardır. Active Record biraz daha sade ve basitken; Data Mapperda işin içine repositoryler girmektedir. Bu yüzden küçük ölçekli uygulamalar için Active Record, büyük ölçekli uygulamalar içinse Data Mapper daha uygun diyebiliriz. Yine de aralarında çok büyük bir seçim farklılığı yok diyebilirim. Klasik maddesel bilgileri daha fazla uzatmak istemiyorum bizim için ilk cümle dahi oldukça yeterliydi. önemli olan aslında uygulama kısmı çünkü kavram tanımlarını irdelemek yerine birlikte yapısını incelersek daha iyi anlayabiliriz. Tabi siz daha fazla ayrıntı isterseniz kendi sitesinden inceleyebilirsiniz. Gelelim asıl bölüme.

TypeScript TypeORM Veritabanı Bağlantısı

Bir veritabanı bağlantısı için veritabanı platformunun seçimi, veritabanımızın kullanıcı adı, şifre gibi parametrelerini ayarlama işlemlerini tamamladıktan sonra yapmamız gereken ilk eylem, proje içerisinden bu veritabanına bağlantının gerçekleştirilmesidir. Bağlantıyı gerçekleştirmek için ise bir bağlantı yani connection nesnesinden faydalanırız.

Yazıyı basit tutmaya karar vermiştim fakat bir örnek üzerinde anlatılanları implemente edersek sanırım projesinde bir an önce veritabanı bağlantısı oluşturmak isteyenlerin sabırsızlığını giderebilirim diye düşündüm. Bu sebeple neyi neden yaptığımızı anlatırken örnek bir proje üzerinde de adımların sonuçlarını tek tek göstereceğim.

Halihazırda bir projeniz varsa “Connection Nesnesi Oluşturma” kısmından devam edebilirsiniz.

Ön Hazırlık

Veritabanının Oluşturulması:

MySQL new database

Benim seçtiğim platform MySQL olduğu için burada yeni bir database oluşturuyorum. Tabi henüz kendisi çok taze olduğu için ne tablosu var ne verisi… Ama merak etmeyin bunları hemen TypeORM yardımıyla ekleyeceğiz.

TypeORM’u Projeye Dahil Etme

TypeORM’u projemizde kullanabilmek için package.json dosyamızdaki dependencies kısmına “typeorm”: “0.2.24” satırını ekliyoruz. Sonrasında $npm install komutunu çalıştırıp dependencieste yaptığımız değişiklikleri uyguluyoruz.

Şunu unutmayalım ki biz TypeORM üzerinden bir veritabanı ile ilişkilendirme yapacağız dolayısıyla bu veritabanına ilişkin paketi de projemize dahil etmemiz gerekiyor aksi taktirde “DriverPackageNotInstalledError” hatası alabiliriz. Bunu önleyebilmek için ben MySQL kullandığımdan mysql paketini ekleyeceğim. Bunun için konsoldan

$npm install mysql — save komutunu kullanıyorum.

Artık TypeORM kütüphanesini kullanmaya hazırız.

Connection Nesnesi Oluşturma

Boş databaseimize sahip olduğumuz için artık TypeScript tarafına geçerek onu doldurmaya başlayabiliriz. Yukarıda da belirttiğim gibi proje içerisinden veritabanına erişmek için bağlantı yani connection nesnesinden faydalanmaktayız.

TypeORM ile connection nesnesi oluşturmak için birkaç farklı yol mevcuttur. Bunlardan en yaygın olanı createConnection’dır. Veritabanı ile ilgili yapacağımız işlemleri bağlantı kurma, CRUD vs. database.ts adını verdiğimiz bir dosya içerisinde gerçekleştirebiliriz. TypeORM üzerinden bir connection yaratacağımız için tabii ki bunu import etmemiz gerekmekte. Import edeceğimiz başka şeyler de olacak fakat bunu onlardan bahsederken söyleyeceğim.

Not: Yukarıdaki kodda bir top-level await mevcuttur. Bunun kullanılabilmesi için tsconfig.json’da target’ın es2017 ve üzeri, module’ün ise esnext ya da system olması gereklidir. Eğer proje yapınız bunlardan herhangi birini sağlamıyorsa connectionı şu şekilde de oluşturabilirsiniz.

Evet artık veritabanına bağlanmamızı sağlayacak bağlantı nesnemiz hazır. O halde bağlandıktan sonra örnek olarak bir ekleme işlemi yapalım ve bu connection nesnesini nasıl kullandığımızı görelim.

Bu noktada koddaki entities kısmı tabii ki uyarı verecektir çünkü henüz bir database model sınıfı oluşturmadık. O halde bir sonraki adıma geçip o konuya el atalım.

Database Model Sınıfı Oluşturma

Herhangi bir veritabanında işlem yapmak için temel anlamda 2 şey gereklidir: Veritabanına bağlantı ve üzerinde ekleme, silme, güncelleme gibi işlemlerin gerçekleştirileceği bir nesne. Bu durumda elimizde bir bağlantı nesnesi varken bir de veritabanımızdaki bilgileri yazılım tarafımızda temsil edecek bir varlık sınıfına ihtiyaç duyarız ki bu sınıftan ürettiğimiz her nesne veritabanımıza kaydedilecek alanları içeren bir satırdır aslında.

Asıl amacımız veritabanında gerçekleştireceğimiz işlemleri anlatmak olduğundan veritabanı varlıklarımız az alanlı bir sınıfa ait olsun. Varlıklarımızı tutmak adına “entities.ts” adında bir dosya oluşturalım ve içine de Person adında bir sınıf yaratalım.

Öncelikle bir varlık sınıfı oluşturacağımız için TypeORM kütüphanesinin barındırdığı gerekli metodları import ediyoruz.

Yukarıdaki kodda Entity anahtarı aslında sınıfımızdan üretilecek nesnelerin veritabanına uygun şekilde haritalanmasını sağlamaktadır. Biz de Entity() ile sınıfımızı bir varlık sınıfı olarak işaretliyoruz. PrimaryGeneratedColumn ise veritabanında gerçekleşen her bir ekleme işleminde otomatik artan bir id değerinin üretilmesi için kullanılmakta. Column dediğimiz yapı veritabanımızdaki sütunlardır. Örneğin, veritabanı kaydımızda kişinin adı, yaşı gibi bilgiler tutulacaksak bunlar bir kayıt yani bir satır için farklı sütunları temsil eder. Bu durumda tüm özelliklerimiz için Column diyerek veritabanındaki sütunları temsil edeceğini belirtiyoruz. Artık veritabanına ekleyeceğimiz kayıtlarımızı, uygun şekilde haritalayacak aracı sınıfımızı oluşturduk. Yapacağımız her işlemi, bu sınıfın nesneleri üzerinden gerçekleştireceğiz.

Connectionımızı oluştururken entities bölümüne Entity() sınıfımızın adını yazmayı unutmayalım. Bu örnek için entities: [Person] şeklinde olacak. Oluşturduğumuz Person sınıfını database.ts’de import etmeyi unutmayalım. Dosyalarımızın src gibi bir klasörde tutulduğunu varsaydığımızda import { Person } from ‘./entities’; diyerek bunu gerçekleştirebiliriz.

Varlık sınıfımızın oluşturulması aşamasında eğer projemiz yeni oluşturulmuş ve tam anlamıyla konfigüre edilmemiş bir proje ise:“ Experimental support for decorators is a feature that is subject to change in a future release.” gibi bir hata almamız olası. Bu noktada “tsconfig.json” dosyamızdaki compilerOptions’ımıza “experimentalDecorators”: true, “experimentalDecorators”: true ve “strictPropertyInitialization”: false satırlarını ekleyerek hatalardan kurtulabiliriz.

TypeORM’da CRUD işlemleri yapabilmek için kolay anlamda 2 farklı seçenek izleyebiliriz. Bunlar EntityManager ve Repository kavramlarıdır. Bunlar, veritabanından bağımsız kılınan ve yazılım tarafında tuttuğumuz veritabanı nesnelerimizin(Person adlı varlık sınıfından türettiğimiz nesneler) yönetimini sağlamaya dair kavramlardır. İşlemlerimizi gerçekleştirirken bir varlık yöneticisi ya da bir depo (repository) yaklaşımını kullanabiliriz. Varlık yöneticisi tüm varlık sınıfı depolarının tek bir yerde toplanmasına benzeyen bir yapıdır. Ben örneklerde repository yaklaşımını kullanacağım.

Repository, her bir varlık sınıfı için özel bulunan veri depolarını temsil eder. Bunu şu şekilde düşünebiliriz: Person sınıfıyla çalışırken bu sınıftan türettiğimiz kişilerimizi bir veri havuzunda tutuyoruz. Herhangi bir kişiyle ilgili herhangi bir işlem gerçekleştireceksek de ilgili kişiyi o veri havuzu üzerinden getirerek bunu gerçekleştiriyoruz.

Bunların dışında Query Builder kullanılarak da ekleme, silme, güncelleme işlemleri yapılabilir fakat bu temel işlemler için ilk 2 yaklaşımı daha basit bulduğumu söyleyebilirim. Query Builder ise daha karmaşık SQL sorgularında kullanabilecek bir yöntem. Biz basit CRUD işlemleri gerçekleştireceğimiz için diğer yöntemlerden devam edelim.

CRUD İşlemleri

Veritabanına Ekleme İşlemi:

Veritabanına ekleme işlemi için öncelikle veritabanına kaydedilecek nesnemizi, varlık sınıfımızdan üretmeliyiz. Aşağıda varlık sınıfından bir nesne ürettik ve bunu ekleme işlemimizi gerçekleştireceğimiz fonksiyonumuza parametre olarak gönderdik. Şimdi eklemenin nasıl gerçekleştirildiği konusuna gelebiliriz.

Ekleme silme işlemlerinden önce eğer connection nesnemizi 2. yolla oluşturmayı tercih ettiysek main fonksiyonumuzda initDatabase() fonksiyonunu çağırmayı unutmamalıyız.

İşlemimiz için bağlantı gerektiğinden önceden oluşturduğumuz connection nesnesine getConnection() metodu ile erişmeliyiz. Ekleme yapmak için bağlantı sağladığımız ilgili veritabanında Person -varlık sınıfının- repositorysini getirmeliyiz. Sonrasında ise fonksiyonumuza parametre olarak gönderilen nesneyi kaydetmeliyiz. Repositoryimize getRepository(İlgiliVarlıkSınıfı) kullanımıyla erişebiliriz.

Insert işlemi syntax’i:

getConnection().getRepository(Person).save(person) şeklinde özetlenebilir.

insert fonksiyonumuzu ise yukarıdaki gibi gerçekleyebiliriz. Burada eğer kaydetme işlemi başarılıysa kişinin veritabanındaki id değerine erişebiliriz.

Ekleme işleminden sonra konsol çıktısı

Veritabanından Silme İşlemi:

Veritabanın silme işlemi de aslında tamamen aynı mantıkta. Öncelikle sileceğimiz verimize repository üzerinden erişim sağladığımızı unutmayalım. Veritabanında yapacağımız her işlemi varlık sınıfı nesnelerimiz üzerinden gerçekleştireceğimizi daha önce söylemiştim. Burada silme fonksiyonumuzu istediğimiz parametre üzerinden gerçekleştirebilir ve fonksiyonumuzu buna uygun yazabiliriz. Ben şimdilik adı “a” olan kişileri silmek istiyorum.

Harika ad ve soyadlarla hazırladığım çabasız veritabanı kayıtları

Fonksiyonumuzu deleteDb(“a”) şeklinde kullanabiliriz.

Silme işleminden sonra konsol çıktısı

Burada delete metodu içine Person bir başka deyişle varlık sınıfımızın hangi alanını referans alarak sileceğimizi ve bu referans değerinin ne olacağını belirtiyoruz.

Silme fonksiyonumuzu kullanacağımız yere import etmeyi unutmayalım.

Veritabanından Okuma İşlemi:

Okuma işleminden sonra konsol çıktısı

Veritabanındaki id üzerinde okuma yapmak için: findOne(id)

Örneğin ilk veri için: findOne(1)

Spesifik değere sahip verilerden ilki için: findOne({alan: değer})

Spesifik değere sahip verilerin hepsi için: find({ name: “jane” })

şeklinde kullanım çeşitlendirilebilir.

Örneğin kişilerin adı ve soyadı üzerinden bir okuma işlemi gerçekleştirelim. Parametre olarak verdiğimiz ada, soyada sahip modelimizi veritabanında getirelim. Tabii ki bunu yaparken bu durumun gerçek hayat senaryolarında çok daha farklı olduğunu unutmadan yapıyoruz. Çünkü aynı ada sahip birçok kişi olabilir.

Eğer getPerson(“jane”, “doe”); dersek çıktımız aşağıdaki gibi olacaktır.

Eğer soyadı parametresini olmayan bir soyadıyla değiştirirsek şu sonucu görmüş oluruz. Böylece herhangi bir işlemde kontrol için “undefined” kullanmamız gerektiğini de görmüş oluyoruz.

Bu fonksiyon ile okumakta bir sıkıntı olmasa da fonksiyon üzerinden return edilen Personı başka bir yerde almak istediğimizde bir problemle karşılaşırız. Konsolde böyle bir kişiye ait kayıt çıktısı olmasına rağmen return edilen değeri bir Person nesnesine atamak istediğimizde bize bir hata üretir çünkü fonksiyonumuz aslında asenkron çalışmaktadır. Dolayısıyla örneğin:

var jane = getPerson(“jane”, “doe”);

console.log(jane.age)

gibi bir kod yazdığımızda burada jane’in undefined olduğunu görebiliriz. Eğer kodlar üzerinde debug yaparsak henüz fonksiyonumuz içerisindeki .then(result => { ile başlayan satıra girmeden main’de yazmış olduğumuz console.log(jane.age) satırına geçtiğini görürüz. Özetle henüz connection nesnesi üzerinden kişi nesnesi getirilmesi tamamlanmadan jane nesnesine undefined bir sonuç atanmış olmakta. Bu şekilde bir çalışma yapacaksanız bu durumu göz önünde bulundurmayı unutmayın.

Veritabanında Güncelleme İşlemi:

Bir verinin veritabanında güncellenmesi için öncelikle güncellenecek veriyi, modeli elimize almamız gerekir.

Aslında update işlemini “üzerine yazma” konusundan faydalanarak gerçekleştirenler de var fakat bizim her bir kaydımız otomatik artan bir id ile kaydedildiğinden ve id değerimiz super key olduğundan bir kayıtta tek bir alanı farklı olarak şekilde tekrar kaydedersek veritabanına yeni bir varlık kaydetmekten başka bir şey yapmış olmayız.

Şimdi jane doe’nin yaşını 25 olarak güncelleyelim. Bunun için kullandığımı metod update() metodu. update metodunun ilk parametresi olarak, güncellemek istediğimiz varlığa ait kriterleri belirtiyoruz. İkinci parametre ise güncellemek istediğimiz alan ve onun yeni değeri şeklinde.

updateDb(“jane”,”doe”, 25); şeklinde fonksiyonumuzu çağırdığımızda:

şeklinde okuduğumuz verilerin

olarak güncellendiğini görebiliriz.

--

--

Burcu S

Flutter Developer, Lover & Learner | Computer Engineer | For contact: linkedin.com/in/burcus/