Node.js ile Uygulama Geliştirme – 7 : REST Mimarisi ve RESTful Servisler Geliştirme [Routing ve Controllers ]

Bir önceki yazımızda, veri tabanı bağlantımızı yazmış ve Sequelize ile modellerimizi tanımlamıştık. Bu yazıda kaldığımız yerden devam edip , ilk controller ve rotalarımızı yazalım. İlk olarak ürün ekleme silme güncelleme ve listeme işlemimiz için, Products Controller ı yazalım. Controllers klasörümüz içinde ProductCtrl.js isimli bir dosya oluşurup controller ı yazmaya başlayalım. Sonrasinda controller a daha yakindan bakip neler olup bitiyor ve routing yapimiz nasil kullaniliyor biraz konusalim.

controllers/ProductCtrl.js

import {DbErrors} from '../libs/helper'

module.exports = (app) => {

    const Product = app.libs.db.models.Products;
    const ProductCtrl = {

        index(req, res, next) {

            Product.findAll()
                .then(products => {
                    const count = products.length;
                     res.status(200).send({count, products})
                })
                .catch(err => res.status(500).send(err))
        },

        find(req, res, next) {
            Product.findById(req.params.id)
                .then(product => {
                     res.status(200).send({product})
                })
                .catch(err => res.status(500).send(err))
        },

        create(req, res, next) {
            Product.create(req.body)
                .then(product => {
                    res.status(201).send({product})
                })
                .catch(err => {
                    res.status(500).send(err))
                })
        },

        update(req, res, next) {
            Product.update(req.body, {where: {id: req.params.id}})
                .then(count => {
                    const updated = count[0];
                    let message = updated >= 1 ? 'Guncelleneme Basarili' : 'Eslesen bir kayit bulunamadi'
                    return res.status(200).send({updated, message})
                })
                .catch(err =>  res.status(500).send(err))
        },

        destroy(req, res, next) {
            Product.destroy({where: {id: req.params.id}})
                .then(count => {
                    let message = count >= 1 ? 'Kayit basariyla silindi' : 'Eslesen birkayit bulunamadi'
                    res.status(200).send({count, message})
                })
                .catch(err => res.status(500).send(err))
        }
    }

    app.route('/api/products')
        .get(ProductCtrl.index)
        .post(ProductCtrl.create)

    app.route('/api/products/:id')
        .get(ProductCtrl.find)
        .delete(ProductCtrl.destroy)
        .put(ProductCtrl.update)
}

Controller ve routing hakkında konusmaya başlamadan önce , bu haliyle kayıt edip uygulamamızı çalıştıralım, browser dan yada Postmen gibi bir araçtan aşağıdaki adrese bir istek gönderelim;

http://localhost:3000/api/products

get_ request demo
Yukaridaki goruntuyu elde ettiyseniz, controller ve rotalamamiz dogru bir şekilde calismakta. Controller yapimiz , 3 aşağı 5 yukarı diğer modellerimiz içinde aynı olacağı için , product controller a yakından bakalım ve ne olup bittiğini daha detaylı olarak konuşalım. Böylece genel olarak süreç daha netleşmiş olacaktır.

İlk olarak; Controller icin de DB işlemlerimizi yapabilmek için ilgili modelimize bi referans oluşturduk.

const Product = app.libs.db.models.Products;

sonrasında controller ın gövdesi geliyor, aslında bu şekilde yaızm sadece daha düzenli olması için bir Object olarak sarmalanmasından ibaret. Ayrıca okuma kolaylığı da sağlamakta. Yoksa aşağıdaki kullanım zorunlu değil, sonuçta Javascript yazdığımız için controller gövdesi içinde her controller ı direkt olarak ayrı ayrı fonksiyonlar şeklinde de yazabilirdik.

const ProductCtrl = {

    index(req, res, next) {
      ...

Controller gövdemize baktığımızda, 5 tane metod tanımladığımızı göreceksiniz. index , find , create, update ve destroy. Bu isimlendirme ve metod sayısıda tamamen size kalmış ben en çok kullanılan ve Rest mimariye uygun olan isimlendirmeleri kullandım. Hem genel Restfull mimari açısından hem de bu mimariyi gerçekleyen bir çok populer framework un kullandığı(Laravel, Rails vs.) genel yapıyı kullanmış olduk.

  1. index : Tüm controller larımızın giriş metodu, kullandığımız Resource / Kaynağın (bizim örneğimiz de ürünler) liste olarak geri döndürüldüğü metod.
    1. Http Verb : GET , Url: /api/products , resource/kaynak: urunler listesi / products
  2. find :  kullandığımız Resource / Kaynağın , tek bir adedini “id”  alanını baz alarak geri döndürecek metodumuz
    1. Http Verb : GET , Url: /api/products/:id , resource/kaynak:  1 urun / products
  3. create : kullandığımız Resource / Kaynağa, yeni bir ürün ekleyecek metodumuz
    1. Http Verb : POST, Url: /api/products/ , resource/kaynak:  1 yeni urun / products
  4. update : kullandığımız Resource / Kaynağı, güncelleyecek metodumuz. Ürün “id” alanını baz alacak
    1. Http Verb : PUT, Url: /api/products/:id , resource/kaynak:  1 urun güncelle / products
  5. destroy: kullandığımız Resource /Kaynağı silen metodumuz. Ürün “id” alanını baz alacak
    1. Http Verb : DELETE, Url: /api/products/:id , resource/kaynak:  1 urun sil / products

Yukarıdaki listeyi biraz daha konuşalım. Genel olarak Restful yapı ile hiç ilginlenmediyseniz aşağıdaki genel kavramları bir hatırlayalım;

  1. Restful mimari bir , framework yada bir zorunluluk değil hatta bazıları bir protokol  diye isimlendirsede tam anlamıyla bu kavramıda doldurduğunu söyleyemeyiz. Daha çok “bir yaklaşım” demek daha doğru olacaktır. Bu adresten güzel bir türkçe makaleye ulaşabilirsiniz.
  2. Restful mimariyi tanımlayan iki önemli, temel kavram var, Resource / Kaynak ve HTTP Verbs – Http metodları
    1.  Resource / Kaynak : Kabaca, uygulama seviyesindeki her bir DB modelimiz( örneğin bizim uygulamamızdaki, Product modeli gibi)
    2. HTTP Verbs : Her bir kaynak üzerinde gerçekleştireceğimiz işlemler için ilgili http metodları kaynak üzerinde eşleştirmek / kullanmak. “get, post, put, delete”

Restful mimari için daha fazla şey söylemek mümkün ama pratikte, en önemli iki noktası bu iki başlık diyebiliriz. Bizde Controller ı bu şekilde yazmaya çalıştık, ilk kısım olan “Resouce / Kaynak”  tanımlamalarımızı , yukarıdaki 5 metodumuz ile oluşturmuş olduk. Metodlarımızın içinde verilerimizi aldık yada işledik(silmek, güncelemek gibi). Metodlarımızı konuşmadan önce, yukarıdaki ikinci kısmı nasıl gerçekleştirdiğimize bakalım;

İkinci, kavramımız olan “HTTP Verbs”, yani ilgili http metodunu , ilgili metoda bağlama işlemi ni aşağıdaki kısım ile oluşturduk;

app.route('/api/products')
    .get(ProductCtrl.index)
    .post(ProductCtrl.create)

app.route('/api/products/:id')
    .get(ProductCtrl.find)
    .delete(ProductCtrl.destroy)
    .put(ProductCtrl.update)

Dikkat ettiyseniz, iki kısım olarak rotalarımızı ilgili metodlarımızla örtüştürdük. İlk kısım, parametre olarka “id” alanı almayan iki metodumuzu temsil etmek.(index ve create). İknci kısım ise diğer 3 metodumuz id parametresine ihtiyaç duyduğu için, beraber tanımladık. Controller Metodlarımızı ve HTTP metodlarımızı nasıl örtüştürdüğümüzü görebeilirsiniz. (put(ProductCtrl.update)  gibi.)

Bu arada, express framework ü kullandığımız için controller içinde ilgili roueting veya http verb örtüşmesini de yapabildik. Normalde, diğer popüler frameworklerde (Rails, Laravel vs.) routing işlemi ayrı ve tek bir dosyada tutulur. Nodejs / Express dünyasından çok yaygın olan MVCR(model, view,controller, router) tasarım kalbını kullanmış olduk. Aslında klasik MVC den tek farkımız, rota tanımlamarınıda Controller içinde yapmış olmamamız. Express yeterince esnek olduğu için isterseniz bu tanımlamaları, tıpkı diğer klasik MVC Frameworkler gibi ayrı bir dosyaya alabilirisiniz.(MVC)

Fakat bu yapı(MVCR) pratikden söyleyebileceğim kadarıyla çok daha kolay ve esnek. Ayrıca, express rotalarınızı ayrı bir dosyada da tanımlayabilme yada gruplama imkanıda verdiği için(ileride bunuda kullanacağız) iki yaklaşımın getirdiği kolaylıklarıda beraber kullanabileceğiz.  Son olarak bir şeyi daha belirtelim;

Rota tanımlamalarımıza, ilgili metodumuzun referansını verdiğimizi unutmayın, başka bir değişle metodları çağırmadık. Express ilgili rota örtüşmesi olduğunda, otomatik olarak ilgili metodu bizim için çağıracaktır.

Referans : ProductCtrl.find  , Metod çağırma : ProductCtrl.find()  

Şimdi metodlarımıza bakarak devam edelim ilk olarak, index() metodumuzla başlayalım;

index(req, res, next) {

    Product.findAll()
        .then(products => {
            const count = products.length;
            res.status(200).send({count, products})
        })
        .catch(err => res.status(500).send(err))
},

index metodumuza baktımızda, tekrar edecek olursak, çalışacağı şart “/api/products” a yapılacak GET requests/istekleri olacaktır. Daha önceki yazılarda belirtmiştik, req,res ve next isimli 3 değişkenimiz, express tarafından rotalama sürecinde  tüm metodlarımıza otomatik olarak sağlanacaklar.

Rota tanımlamalarımızda, ilgili metodunun referansını kullanmamızın bir nedenide bu. İlgili metod bu referansla çağrılırken, bu 3 değişkenimiz de otomatik olarak aktarılacakalar.

 Product.findAll()
        .then(products => {
            const count = products.length;
            res.status(200).send({count, products})
        })

Yukarıdaki kısım ise, Sequelize modelimizi kullanıp db de ki tüm ürünleri sorguladığımız alan. findAll()  metodu, Sequelize ın bize sağladığı bir yardımcı metod.

Şuan itibariyle pagination/Sorting – Sayfalama/Sıralama gibi bir işlem yapmadan tüm ürünleri sorguluyoruz. İleride, sıralama ve sayfalama gibi alanlarıda ekleyeceğiz. Sequelize, sorgu sonucunda bir Promise döndürdüğü için (klasik call-back yerine)  verilerimize .then() metodu içinde ulaşıyoruz.

Sorgudan sonra dönen veri .then(products) şeklinde metodumuzun ilk parametresi olarak erişilebilir olacaktır. products yerine isted’ğiniz isimi verebilirsiniz ama mantıklı olan kaynağın kendinisi isim olarak kullanmak olacağı için biz products ismini değişken ismi olarak kullandık.

.catch(err => res.status(500).send(err))

Bloğu ise, DB sorgusu sırasında bir hata oluşursa, çalışacak bloğumuz. Eğer her hangi bir hata olmaz ise hiç bir zaman çalışmayacaktır. Hata olması durumunda, şuan itibariyle hata mesajını olduğu gibi istemciye gönderiyoruz. Bu durum normalde isteyeceğimiz bir durum değil.

Uygulamamızın daha iyi bir hata yönetim sistemine sahip olması adına hatalarımızı hemen olduğu yerde işlemek yerine , ilgili birime aktarıp daha düzenli bir hata sistemine yazımızın devamında geçeceğiz. Her hangi bir hatamız olmadığında ise metodumuz içinde then() bloğunda, isteğimizi cevaplıyoruz;

const count = products.length; // Kac tane urun bulduk?
res.status(200).send({count, products}) // buldugumuz urunleri istemciye yolla

Yukarıda, isteği sonlandırmak için res nesnemizin iki alt metodunu kullandık “status” ve “send” . Status metodu, gelen isteğin durumunu tutacağımız “header/başlık” bilgilisini cevaba(response) ekleyecektir.

Bizim örneğimiz de 200 nolu http kodu ile, gelen isteğin başarılı bir şekilde işlendiğini istemcimizin bilgisine sunuyoruz. http cevap kodlarımızın tamamına buradan bakabilirsiniz. Bu kodları kullanmak teknik olarak zorunlu değil ama pratikde her zaman kullanmanız hem sizin için hemde istemciler için çok faydalı olacaktır.

Son olarak, send() metodumuzla isteği sonlandırıp, cevabımızı istemciye yolluyoruz. cevabımız, her türlü geçerli Javascript nesneni yada string/text olabilir. eğer express, cevap olarak bir javascript nesnesi yolladığınızı görürse cevabı otomatik olarak json formatında yollayacaktır.

Yani ayrıyeten bir değişikli yapmanıza gerek yok fakat, bu işlemi daha belirgin hale getirmek yada her şartta json gönderimini garanti altına almak adına metodumuzdaki ilgili kısmı şu şekilde değiştirebilirisiniz.

res.status(200).json({count, products})

sadece send yerine json metodunu kullandık. hangisini kullanmak isterseniz onu kullanabilirsiniz. tekrar edecek olursak, send() metodu gönderiğiniz içeriğe göre cevap formatını otomatik olarak tanımlamaya çalışacaktır(bizim örneğimiz de, bir Javascript nesnesi olduğu için yine json cevaplama yapılacaktır.).  json() metodu ise direkt olarak json formatında yollayacaktır.

Diğer metodlarımızda ortak olan kısımları geçip farklılıklara bakarak devam edelim,

find(req, res, next) {
    Product.findById(req.params.id)
        .then(product => {
            res.status(200).send({product})
        })
        .catch(err => res.status(500).send(err))
},

find metodumuz da sadece iki farklılık söz konusu;

  1. findAll()  yerine .findById(req.params.id)  metodumuzu kullandık. Hatırlarsanız, find metodumuzun görevi tek bir ürünü bulmakti. Bulma kriteride, ürün id si idi. Bu yüzden Sequelize ın findById metodunu kullandık.
  2. findById() metodumuz bir id verisine ihtiyaç duyduğu için istemciden gelen id alanını parametre olarak verdik.
  3. bu metodun eşleştiği url e baktığımızda (‘/api/products/:id’) id alanın nerden geliğini görebiliriz.

create metodumuz için söyleyeceğimiz ilk farklılık bir GET request değil POST request kabul etmesi.

.post(ProductCtrl.create)

Hatırlarsanız, genel olarak rest mimari yapısı içinde yeni bir resource/kaynak oluştururken(yeni bir ürün oluşturuyoruz) kullandığımız metod POST.

  1. Sequelize tarafında create() metodunu kullandık. DB ye yeni bir kayıt eklemek için kullanacağımız metod.
  2. cevabınızın http kodu ise, 200 değil 201. Yukarıda verdiğim linke baktığınızda, 201 in karşılığı “resource created / yeni bir şey oluşturuldu” şeklinde görebilirsiniz.

update metodumuzun için ise PUT requestlerimizin işlenmesi söz konusu. Bir ürün üzerinde güncelleme yapacağımız için yine bir id alanımız var.

  1. Sequelize tarafında update() metodunu kullandık. DB de  bir kayıt güncellemek için kullanacağımız sequelize metodu.
  2. Klasik SQL yapısı gereği, güncelleme işlemi sonucunda, etkilenen kayıt sayısı döndürülecektir. Bu yüzden, update meteodumuzun aldığı parametreyi count olarak isimlendirdim
    1. .then(count => {   const updated = count[0];
  3. cevap olarak, eğer etkilenen kayıt sayısı 0 dan büyükse, işlemimiz başarılı sayıyoruz ve ona göre bir cevap metni oluşturuyoruz. ‘Guncelleneme Basarili’ : ‘Eslesen bir kayit bulunamadi’  gibi.

destroy  metodumuz ise bir DELETE request e cevap vermekte, belirli bir ürün için işlem yapacğamız için, tıpkı update ve find gibi id alanını beklemekte.

  1. Sequelize tarafında destroy() metodunu kullandık. DB de  bir kayıt silmek için kullanacağımız sequelize metodu.
  2. Sequelize destroy() metodu bir eşleşme sorugusu almakta. {where: {id: req.params.id}}
  3. Klasik SQL yapısı gereği, silme işlemi sonucunda, etkilenen kayıt sayısı döndürülecektir. Bu yüzden, destroy meteodumuzun geri aldığı parametreyi count olarak isimlendirdim
  4. cevap olarak, eğer etkilenen kayıt sayısı 0 dan büyükse, işlemimiz başarılı sayıyoruz ve ona göre bir cavp metni oluşturuyoruz. ‘Kayit basariyla silindi’ : ‘Eslesen birkayit bulunamadi’ gibi.

Metodlarimizin genel olarak iç yapısınıda bu şekilde incelemiş olduk, şimdi  aşağıdaki ekran görüntüsü ile devam edelim;

Görüntümüzden de anlaşılacağı gibi bütün metodlarımız çalışıyor. Görüntünün başlarında, her hangi bir parametre yollamadan, veri eklemeye çalıştığım bir kısım var , bu işlemi yapmaya çalıştığımda, Sequelize modelini oluştururken tanımlandığımız kurallar devreye girdiği için modelimizin ihtiyaçlarını karşılamayan istekler Sequeilze tarafından hata mesajları ile geri çevrildi. Bu mesajlar geliştirme ortamında önemli, şuan itibariyle Product modelimizin ilgili alnalarını hatırlayacak olursak,

models/Product.js

id: {
    type: DataType.INTEGER,
    primaryKey: true,
    autoIncrement: true
},
name: {
    type: DataType.STRING,
    allowNull: false,
    validate: {
        notEmpty: true
    }
},
price: {
    type: DataType.DECIMAL(10, 2),
    defaultValue: 0.00,
    min: 0.00,
    max: 99999.00
},
salesPrice: {
    type: DataType.DECIMAL(10, 2),
    defaultValue: 0.00,
    min: 0.00,
    max: 99999.00
},
stockLevel: {
    type: DataType.INTEGER,
    defaultValue: 20
}

Sadece ürün isminin zorunlunlu olduğunu diğer alanların ya opsiyonel yada her hangi bir değer sağlanmadığında, bizim tanımladığımız default/varsayılan değerlerle doldurulduğunu görebiliriz. isim alanı olmayan bir istek gönderdiğimde, Sequelize bize şu hatayı geri döndürdü;

{
    "error": {
    "message": "notNull Violation: name cannot be null",
        "error": {
        "name": "SequelizeValidationError",
            "message": "notNull Violation: name cannot be null",
            "errors": [
            {
                "message": "name cannot be null",
                "type": "notNull Violation",
                "path": "name",
                "value": null
            }
        ]
    }
}
}

Yukaridaki hata mesajı , express yada node.js ile alakalı değil, DB işlemlerimizi yöneten Sequelize ile alakalı, Sequelize yerine başka bir ORM kullanırsanız, onundan buna benzer kendine has hata mesajları denetimleri olacaktır.

Yukarıdaki hata mesajımız geliştirme ortamı için ideal, hatanın detayı gayet açık ve geliştirici olarak bilmemizde / görmemizde fayda var. Ama uygulamamız gerçek dünyaya açıldığında kullanıcılara hem güvenlik açısından hemde bir çok kişi için bu mesajın bi anlamı olmayacağı için daha düzgün bir hata mesajı yollamamız gerek.

Biz iki farklı yöntemle hata ayıklama işlemini gerçekleştireceğiz. Hatalarımızı 2 gruba aktaracağız, node.js ve express ile ilgili hataları, next() metodunu kullanarak error.js deki ilgili hata middleware lerine aktarcağız. DB hatalarımız daha fazla özen gerektirdiği için(güvenlik adına) onları ayrı olarak işleyeceğiz.

İlk olarak DB hatalarımızı filitreleyen  yardımcı bir sınıf yazalım, bu sınıf DB hatalarını geliştirme ortamında olduğu gibi göstersin, production da ise filitreleyip daha anlaşılır sade bir mesaj halinde istemciye göndersin. Bunun için libs klasörü altında, helper.js isimli bir doysa oluşturalım.

libs/helper.js

export class DbErrors{
    static getError(err){
        //Gelistirme ortamindaysak hatayi oldugu gibi geri gonder
        if(process.env.NODE_ENV ==='development'){
            return err;
        }
        //Sequelize tarafindan firlatilmasi muhtemel hatalarin listesi
        //Bu listeyi Sequelize dokumanlarina bakarak genisletebilirsiniz   
        const validationerrorList = ['SequelizeUniqueConstraintError','SequelizeValidationError','SequelizeForeignKeyConstraintError']
        if(!err.name ){// eger hata mesajimiz bir name ozelligi icermiyorsa, ciddi bir sorun vardir demek
            // bu durumda hic bir detayi istemciye gondermiyoruz
            return "istenmeyen bir durumla karsilasildi!";
        }
        //name ozelligi iceriyor fakat bizim listemizin disindaysa, 
        //muhtemlen cok onemli bir hata degildir, kibarca istemciyi uyariyoruz 
        if(!validationerrorList.includes(err.name)){
            return "Islem sirasinda istenmeyen bir hata olustu!"
        }
        // bu noktada bizim hata listemizdeki hatalrdan biri gerceklesmis demektir.
        let error={};
        //hata mesajini istedigimiz formata donusturuyoruz
        if(err.name=='SequelizeForeignKeyConstraintError'){
            error.message =`Bu id ile eslesen bir kayit bulunamadi, ${err.parent.detail}`;
        }
        else{
            error = err.errors.map(e => {
                return {
                    message:  e.message,
                    info:`${e.path} : ${e.value}`
                }
            })
            error.message = "Validation Errors!";
        }
        error.status = 400;
        return error; // hatayi istemciye yolluyoruz
    }
}

Öncelikle belirtmekte fayda var, yukarıdaki sınıf tamamen Sequelize ın fırlatacağı hatalara özel, yani Sequelize kullanmadığınızda bir işlevi olmayacaktır. Ama genel olarak hangi ORM i kullanırsanız kullanın, DB hatalarına daha fazla özen göstermeniz gerektiğini belirtmek için ve genel kıyaslama adına iyi bir örnek diyebilriz.

Yukarıdaki sınıfımız getError(err) isimli bir metodu bulunmakta, bu metod, Sequelize ın fırlattığı hatayı üst seviye olarak filitreleyecek. ve ona göre bir hata mesjaı döndürecek. Kod diçinde bazı yorumlar yazdım genel olarak ne yaptığını anlayabilrisiniz.

Biz uygulamamızda DB ile ilgili hatalarımızı bu metodu kullanarak filitreleyip, istemciye öyle göndereceğiz. Bunun için Controller içindeki aşağıdaki satırları ;

.catch(err => res.status(500).send(err))

Şu şekilde değiştirmemiz gerekte;

.catch(err => res.status(500).send(DbErrors.getError(err)))

Böylece db hatalarımızı daha sade bir şekilde istemciye göndereceğiz. Şimdi, bu durumu test edelim, geliştirme ortamında aldığımız hatayı hatırlayın şuna benzer bir hatamız vardı geliştirme ortamımız için;

{
    "name": "SequelizeValidationError",
    "message": "notNull Violation: name cannot be null",
    "errors": [
    {
        "message": "name cannot be null",
        "type": "notNull Violation",
        "path": "name",
        "value": null
    }
]
}

Şimdi yeni hata mesajlarımızı görmek için uygulamamızı “production” modunda çalıştıralım. Bunun için uygulamam çalışıyorsa sonlandırın ve komut satırından önce ;

set NODE_ENV=production

komutu ile uygulamamızı productıon modunda çalıştıracağımızı belirtelim. sonrasında uygulamamızı çalıştıralım;

npm start

uygulamamız production mod ile çalışacaktır;

production deneme

Şimdi, tekrar geçersiz bir bir ürünü eklemeye çalışın, busefer aşağıdaki hatayı alacaksınız;

 {
      "message": "name cannot be null",
       "info": "name : null"
 }

DB hatalarımızı böylece sadeleştirmiş olduk, ben fazla zaman harcamamak adına varsıylan ingilizce mesajları bıraktım. Siz mesajları sadeleştirdiğimiz kısımda, türkçeleştirebilirsiniz. Şimdi uygulamamızı durdurup tekrar development moda alalım ve ordan devam edelim.

 set NODE_ENV=development

Sanırım Controller üzerinde yeterince konuştuk, uygulamamızda kullanacağımız diğer iki Controller da aynı yapıda olacak, metod tanımlamalarımız, rota tanımlamalarımız hemen hemen aynı yapıda o yüzden onları ayrıyeten konuşmaya gerek yok sanırım. İlk olarak basitçe ürünlerimizin stok takibi yapacağımız, Controller ı yazarak devam edelim;

controllers/stock.ctrl.js

import {DbErrors} from '../libs/helper'

module.exports = (app) => {
    const Stock = app.libs.db.models.ProductsStocks;
    const StockCtrl = {

       index(req, res, next) {

            Stock.findAll({})
                .then(products => {
                    return res.status(200).send({count:products.length, products})
                })
                .catch(err => res.status(500).send(DbErrors.getError(err)))
        },

        create(req, res, next) {
            Stock.create(req.body)
                .then(stock => {
                    res.status(201).send({stock})
                })
                .catch(err => res.status(500).send(DbErrors.getError(err)))
        },

         find(req,res,next){
            Stock.find({where:{product_id:req.params.id}
            })
                .then(product => {
                    return res.status(200).send({product})
                })
                .catch(err => res.status(500).send(DbErrors.getError(err)))
        },
          update(req,res,next){
            Product.update(req.body,{where:{id:req.params.id}})
                .then(count => {
                    const updated = count[0];
                    let message = updated >= 1 ? 'Guncelleneme Basarili' :'Eslesen bir kayit bulunamadi'
                    return res.status(201).send({updated, message})
                })
                .catch(err => res.status(500).send(DbErrors.getError(err)))
        },
         destroy (req,res,next){
            Product.destroy({where:{id:req.params.id}})
                .then(count => {
                    let message= count>=1 ?'Kayit basariyla silindi':'Eslesen birkayit bulunamadi'
                    res.status(200).send({count, message})
                })
                .catch(err => res.status(500).send(DbErrors.getError(err)))
        }
    }

    app.route('/api/stocks')
        .get(StockCtrl.index)
        //.post(StockCtrl.create)

   app.route('/api/stocks/:id')
       .post(StockCtrl.create)
       .get(StockCtrl.find)
       .delete(StockCtrl.destroy)
       .put(StockCtrl.update)
}

Sonrasında, kullanıcı bilgilerini tutacağımız User Controller ı yazalım;

controllers/user.ctrl.js

import {DbErrors} from '../libs/helper'

module.exports = (app) => {
    const User = app.libs.db.models.Users;
    const UserCtrl =
    {
        index(req, res, next) {

            User.findAll()
                .then(users => {
                    return res.status(200).send({ count: users.length, users })
                })
                .catch(err => res.status(500).send(DbErrors.getError(err)))
        },
        find(req, res, next) {
            User.findById(req.params.id)
                .then(user => {
                    return res.status(200).send({ user })
                })
                .catch(err => res.status(500).send(DbErrors.getError(err)))
        },

        create(req, res, next) {
            User.create(req.body)
                .then(user => {
                    res.status(201).send({ user })
                })
                .catch(err => res.status(500).send(DbErrors.getError(err)))
        },

        update(req, res, next) {
            User.update(req.body, { where: { id: req.params.id } })
                .then(count => {
                    const updated = count[0];
                    let message = updated >= 1 ? 'Guncelleneme Basarili' : 'Eslesen bir kayit bulunamadi'
                    return res.status(201).send({ updated, message })
                })
                .catch(err => res.status(500).send(DbErrors.getError(err)))
        },
        destroy(req, res, next) {
            User.destroy({ where: { id: req.params.id } })
                .then(count => {
                    let message = count >= 1 ? 'Kayit basariyla silindi' : 'Eslesen birkayit bulunamadi'
                    res.status(200).send({ count, message })
                })
                .catch(err => res.status(500).send(DbErrors.getError(err)))
        }
    }

    app.route('/api/users')
        .get(UserCtrl.index)
        .post(UserCtrl.create)

    app.route('/api/users/:id')
        .get(UserCtrl.find)
        .delete(UserCtrl.destroy)
        .put(UserCtrl.update)
}

Böylelikle diğer iki controller ı da oluşturmuş olduk şimdi 3 Controller ı da test edelim;

Gördüğünüz gibi 3 Controller da beklediğimiz gibi çalışıyor. Controller larımızla henüz işimiz bitmedi tabiki bir kaç ekleme daha yapacağız ama ileriki yazılarda. Buraya kadar yaptıktıklarımızı özetleyecek olursak;

  1. 3 tane Controller oluşturduk
  2. İlgili rota tanımlarını yaptık
  3. DB hatalarımız için bir yardımcı sınıf olsuturduk.

Bir sonraki yazıda önce middleware ve rotuting konusunda bir kaç konuya daha değineceğiz ve sonrasında Login sistemimizi tamamlayacağız inş. Uygulamanın Login sistemide tamamlanmış halini de bir sonraki yazıda bulabilirsiniz

 

10 thoughts on “Node.js ile Uygulama Geliştirme – 7 : REST Mimarisi ve RESTful Servisler Geliştirme [Routing ve Controllers ]

  1. Merhaba,
    Elinize emeğinize sağlık güzel bir yazı serisi. Türkçe kaynak bulmak zor bu konularda.

    Fakat yeni başlayanlar için biraz ağır olmuş. Bazı şeylere tekrar tekrar bakmak gerekiyor.

    Biraz daha 0 dan hiç bişey bilmeyenler için olsa daha iyi olurmuş sanki. Mesela ben node.js kullanarak tavla v.b tarz bir oyun yapmayı düşünüyordum. Ciddi anlamda zorluyor şuan.

    Emeğine sağlık tekrar güzel bir seri.

    1. teşekkürler faydali olmasina sevindim . Tavisyelerini dikkate almaya calisacagim, vaktim oldukca daha giris seviyesindne yailar yazmayada calisirim ins.

      Kolay Gelsin.

  2. Çok sevinirim. Baya bir ufkumuzu açarsınız.

    Hatta mümkün ise, oyun programcıları için, böyle düşüncesi olan nerden başlayacağını bilmeyen insanlar içinde genel bir yazı, şunu yapın şurdan başlayın şunu öğrenin gibi bir yazıda muthiş olur.

    Hatta ve hattta var ise öneriniz almak isterim. Mail adresim de var. Zahmet olmaz ise bir yönlendirme yaparsanız sevinirim.
    Online çok kullanıcılı sistemler ile ilgili.

    Teşekkürler

    1. Birkac yazi yazilmasi gerekenbir konu bu, insallah vakit bulunca bir ornek uygulamayla birlikte yazmaya calisacagim

  3. Hocam bir sonraki yazınızı bulamadım acaba varmı yoksa ne zaman bizlere sunacaksınız.Elinize emeğinize sağlık..

Leave a Reply

Your email address will not be published. Required fields are marked *