Bu yazıda PostgreSQL ve Go dili kullanarak basit CRUD işlemlerini yapabileceğimiz bir Restful API oluşturacağız. Yazı, Go hakkında temel düzeyde bilgisi olanlara hitap etmektedir. Eğer henüz Go ile tanışmadıysanız, tanışmak için bu yazıyı okuyup sonra tekrar buraya dönmenizi tavsiye ederim 🙂
Yukarıda bağlantısını verdiğim linkteki Go kurulumu, ortam değişkeni ayarlamaları gibi adımları geçtiyseniz, PostgreSQL kurulum ve yapılandırmalarını da tamamlayıp kodlamaya başlayabilirsiniz.
Gelen isteklerin alınması ve yönlendirilmesi için router ihtiyacımızı gorilla/mux paketini kullanacağız. Paketi yüklemek için ;
go get –u github.com/gorilla/mux
komutunu çalıştırıyoruz.
Veri tabanı bağlantımızı sağlamak ve veri tabanı işlemlerimizi hazırlamak için Go için geliştirilmiş bir ORM paketi olan jinxhu/gorm ‘u kullanacağız. GORM ‘u da çok rahat ve kolay bir şekilde kullanabilmemiz için çok başarılı bir dokümantasyonu var. Paketi yüklemek için;
go get -u github.com/jinzhu/gorm
Kullanıcı kimlik doğrulama işlemlerini yönetebilmek için bir JWT ( JSON Web Token hakkında daha fazla bilgi için bu bağlantıyı kullanabilirsiniz ) paketine ihtiyacımız var. Bu ihtiyacımızı da dgrijalva/jwt-go paketi ile gidereceğiz. Paketi yüklemek için;
go get –u github.com/dgrijalva/jwt-go
Environment dosyamızı (.env) yükleyebilmek için joho/godotenv paketini kullanacağız. Paketi yüklemek için;
go get –u github.com/joho/godotenv
Bu komutlarla birlikte, tüm paketlerimizi $GOPATH altına kurmuş oluyoruz.
Şimdi projemizin dosya yapısına göz atalım.
İşimize sık sık yarayacak, ufak fonksiyonlarımızı yazacağımız “util.go” dosyamızı yaratalım. Bu dosyaya JSON mesajlarını döndüren “Message” ve JSON yanıtlarını döndüren “Respond” fonksiyonlarını yazalım.
package utils import ( "encoding/json" "net/http" ) func Message(status bool, message string) map[string]interface{} { return map[string]interface{}{"status": status, "message": message} } func Respond(w http.ResponseWriter, data map[string]interface{}) { w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(data) }
Veri tabanı bağlantı bilgilerimizi bulunduracağımız “.env” dosyasını yaratıyoruz ve yapılandırma ayarlarımızı yazıyoruz;
db_name = testapiproject db_pass = 1 db_user = postgres db_type = postgres db_host = localhost db_port = 5433 token_password = ks_2tcVkYTIDmwr7895_MYKofCY56SyQK56HhL5TND1TJLXfn41nxuT0OK0
Şimdi de kimlik doğrulamalarını yapacağımız “auth.go” dosyamızı yaratacağız. Ancak model ve utils gibi dosyalara erişebilmemiz için projemize bir modül yaratmamız ve isim vermemiz gerekiyor. Bunun için;
go mod init projeAdi
komutunu kullanıyoruz.
package app import ( "context" "fmt" "GoRestProject/models" u "GoRestProject/utils" "net/http" "os" "strings" jwt "github.com/dgrijalva/jwt-go" ) var JwtAuthentication = func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { notAuth := []string{"/api/user/new", "/api/user/login"} // Doğrulama istemeyen endpointler requestPath := r.URL.Path // mevcut istek yolu // Gelen isteğin doğrulama isteyip istemediği kontrol edilir for _, value := range notAuth { if value == requestPath { next.ServeHTTP(w, r) return } } response := make(map[string]interface{}) tokenHeader := r.Header.Get("Authorization") // Header'dan token alınır if tokenHeader == "" { // Token yoksa "403 Unauthorized" hatası dönülür response = u.Message(false, "Token gönderilmelidir!") w.WriteHeader(http.StatusForbidden) w.Header().Add("Content-Type", "application/json") u.Respond(w, response) return } splitted := strings.Split(tokenHeader, " ") // Token'ın "Bearer {token} / Token {token}" formatında gelip gelmediği kontrol edilir if len(splitted) != 2 { response = u.Message(false, "Hatalı ya da geçersiz token!") w.WriteHeader(http.StatusForbidden) w.Header().Add("Content-Type", "application/json") u.Respond(w, response) return } tokenPart := splitted[1] // Token'ın doğrulama yapmamıza yarayan kısmı alınır tk := &models.Token{} token, err := jwt.ParseWithClaims(tokenPart, tk, func(token *jwt.Token) (interface{}, error) { return []byte(os.Getenv("token_password")), nil }) if err != nil { // Token hatalı ise 403 hatası dönülür response = u.Message(false, "Token hatalı!") w.WriteHeader(http.StatusForbidden) w.Header().Add("Content-Type", "application/json") u.Respond(w, response) return } if !token.Valid { // Token geçersiz ise 403 hatası dönülür response = u.Message(false, "Token geçersiz!") w.WriteHeader(http.StatusForbidden) w.Header().Add("Content-Type", "application/json") u.Respond(w, response) return } // Doğrula başarılı ise işleme devam edilir fmt.Sprintf("Kullanıcı %", tk.Username) // Kullanıcı adı console'a basılır ctx := context.WithValue(r.Context(), "user", tk.UserId) r = r.WithContext(ctx) next.ServeHTTP(w, r) }) }
Kimlik doğrulama kontrollerimizi de yazdıktan sonra artık kullanıcı yaratma ve login endpointlerini yazabiliriz. Bunun için öncelikle veri tabanı bağlantılarımızı yapacağımız ve veri tabanı migrasyon işlemlerinin yapılacağı temel model dosyamızı yaratıyoruz. “models/base.go”
package models import ( "fmt" "os" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/postgres" "github.com/joho/godotenv" ) var db *gorm.DB //database func init() { e := godotenv.Load() //Load .env file if e != nil { fmt.Print(e) } username := os.Getenv("db_user") password := os.Getenv("db_pass") dbName := os.Getenv("db_name") dbHost := os.Getenv("db_host") // Connection stringi yaratılır dbUri := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable password=%s", dbHost, username, dbName, password) // Eğer Heroku üzerinde bir PostgreSQL'e sahipseniz, bu ayarlamaları yapmak yerine doğrudan // heroku tarafından verilen database url'i kullanabilirsiniz // dbUri := fmt.Sprintf("postgres://xxxxx@xxx.compute.amazonaws.com:5432/ddjkb1easq2mec") // Database url fmt.Println(dbUri) conn, err := gorm.Open("postgres", dbUri) if err != nil { fmt.Print(err) } db = conn db.Debug().AutoMigrate(&Account{}) //Database migration } //returns a handle to the DB object func GetDB() *gorm.DB { return db }
Yukarıdaki kodu yazdığımızda 38. satıra denk gelen “db.Debug().AutoMigrate(&Account{})” kodu, Account modeli henüz varolmadığı için hata verecektir ama sorun değil, biraz sonra onu da yaratacağız.
Go projemiz çalıştırıldığında, “init” fonksiyonu otomatik olarak çalıştırılacak ve environment dosyamıza yazmış olduğumuz bilgiler ile veritabanı bağlantımız yapılmaya çalışılacak, eğer sorun yok ise bu sefer belirttiğimiz modeller kullanılarak veritabanı migrasyonu yapılacak.
Veri tabanı bağlantısı, migrasyonlar ve kimlik doğrulama işlemlerini tamamladıktan sonra artık projemizin ilk çalışacak kısmı olan “main.go” dosyamızı yazabiliriz.
package main import ( "GoRestProject/app" "fmt" "net/http" "os" "github.com/gorilla/mux" ) func main() { router := mux.NewRouter() router.Use(app.JwtAuthentication) // Middleware'e JWT kimlik doğrulaması eklenir port := os.Getenv("PORT") // Environment dosyasından port bilgisi getirilir if port == "" { port = "8000" //localhost:8000 } fmt.Println(port) err := http.ListenAndServe(":"+port, router) // Uygulamamız localhost:8000/api altında istekleri dinlemeye başlar if err != nil { fmt.Print(err) } }
Veritabanı ve api’ımızın ana uç noktası hazır olduğuna göre artık kullanıcı modelini yaratabiliriz. “models/accounts.go” dosyasının en başına migrasyon esnasında, veri tabanına yaratılmasını istediğimiz structı ekleyeceğiz. Bu strunctın bir elemanı da “gorm.Model” olacak. Bu elemanın bulunmadığı structlar, migrasyonla veri tabanında yaratılmazlar.
package models import ( u "GoRestProject/utils" "os" "strings" "github.com/dgrijalva/jwt-go" "github.com/jinzhu/gorm" "golang.org/x/crypto/bcrypt" ) // JWT struct type Token struct { UserId uint Username string jwt.StandardClaims } // Kullanıcı tablosu struct type Account struct { gorm.Model // Migrasyon işlemi yapılırken, veritabanı üzerinde accounts tablosu yaratılması için belirtilir Email string `json:"email"` Password string `json:"password"` Token string `json:"token";sql:"-"` } // Gelen bilgileri doğrulama fonksiyonu func (account *Account) Validate() (map[string]interface{}, bool) { if !strings.Contains(account.Email, "@") { return u.Message(false, "Email adresi hatalıdır!"), false } if len(account.Password) < 8 { return u.Message(false, "Şifreniz en az 8 karakter olmalıdır!"), false } temp := &Account{} // Email adresinin kayıtlı olup olmadığı kontrol edilir err := GetDB().Table("accounts").Where("email = ?", account.Email).First(temp).Error if err != nil && err != gorm.ErrRecordNotFound { return u.Message(false, "Bağlantı hatası oluştu. Lütfen tekrar deneyiniz!"), false } if temp.Email != "" { return u.Message(false, "Email adresi başka bir kullanıcı tarafından kullanılıyor."), false } return u.Message(false, "Her şey yolunda!"), true } // Kullanıcı hesabı yaratma fonksiyonu func (account *Account) Create() map[string]interface{} { if resp, ok := account.Validate(); !ok { return resp } hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(account.Password), bcrypt.DefaultCost) account.Password = string(hashedPassword) GetDB().Create(account) if account.ID <= 0 { return u.Message(false, "Bağlantı hatası oluştu. Kullanıcı yaratılamadı!") } // Yaratılan hesap için JWT oluşturulur tk := &Token{UserId: account.ID} token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) tokenString, _ := token.SignedString([]byte(os.Getenv("token_password"))) account.Token = tokenString account.Password = "" // Yanıt içerisinden parola silinir response := u.Message(true, "Hesap başarıyla yaratıldı!") response["account"] = account return response } // Giriş yapma fonksiyonu func Login(email, password string) map[string]interface{} { account := &Account{} err := GetDB().Table("accounts").Where("email = ?", email).First(account).Error if err != nil { if err == gorm.ErrRecordNotFound { return u.Message(false, "Email adresi bulunamadı!") } return u.Message(false, "Bağlantı hatası oluştu. Lütfen tekrar deneyiniz!") } err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(password)) if err != nil && err == bcrypt.ErrMismatchedHashAndPassword { // Parola eşleşmedi return u.Message(false, "Parola hatalı! Lütfen tekrar deneyiniz!") } // Giriş başarılı account.Password = "" // JWT yaratılır tk := &Token{UserId: account.ID} token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) tokenString, _ := token.SignedString([]byte(os.Getenv("token_password"))) account.Token = tokenString // JWT yanıta eklenir resp := u.Message(true, "Giriş başarılı!") resp["account"] = account return resp } // Kullanıcı bilgilerini getirme fonksiyonu func GetUser(u uint) *Account { acc := &Account{} GetDB().Table("accounts").Where("id = ?", u).First(acc) if acc.Email == "" { // Kullanıcı bulunamadı return nil } acc.Password = "" return acc }
Account modelimizde yazdığımız kullanıcı yaratma ve login fonksiyonlarını kullanmamız için bir de controllera ihtiyacımız var. Bu ihtiyacı da “controllers/authController.go” dosyasını yaratarak gidereceğiz. Bu dosya, router üzerinden kendisine iletilen isteklerin gövdelerini decode edip ilgili modele gönderecek ve modelden gelen yanıtı göndermemizi sağlayacak.
package controllers import ( "GoRestProject/models" u "GoRestProject/utils" "encoding/json" "net/http" ) var CreateAccount = func(w http.ResponseWriter, r *http.Request) { account := &models.Account{} err := json.NewDecoder(r.Body).Decode(account) // İstek gövdesi decode edilir, hatalı ise hata döndürülür if err != nil { u.Respond(w, u.Message(false, "Geçersiz istek. Lütfen kontrol ediniz!")) return } resp := account.Create() // Hesap yaratılır u.Respond(w, resp) } var Authenticate = func(w http.ResponseWriter, r *http.Request) { account := &models.Account{} err := json.NewDecoder(r.Body).Decode(account) // İstek gövdesi decode edilir, hatalı ise hata döndürülür if err != nil { u.Respond(w, u.Message(false, "Geçersiz istek. Lütfen kontrol ediniz!")) return } resp := models.Login(account.Email, account.Password) // Giriş yapılır u.Respond(w, resp) }
Artık, main.go içerisine bu controllerı çağıracak birer handler ekleyebilir ve ardından ilk isteğimizi atabiliriz!
“main.go” içerisinden “router.Use(app.JwtAuthentication)” kodunun altına aşağıdaki kodları ekliyoruz;
router.HandleFunc("/api/user/new", controllers.CreateAccount).Methods("POST") router.HandleFunc("/api/user/login", controllers.Authenticate).Methods("POST")
Yukarıdaki işlemleri de yaptıktan sonra artık “go run main.go” komutu ile api’ımızı başlatabiliriz. Komutu çalıştırmamızın arından veri tabanı bağlantımız yapılacak ve migrasyon işlemi çalışarak tablolarımızı yaratacaktır.
“go run main.go” komutu çalıştırıldığında, eğer Windows işletim sistemi olan bir cihazda çalışıyorsanız bir izin penceresi gelecektir. Bu ekranadan gerekli izni vermelisiniz.
Yeni Kullanıcı hesabı oluşturmak için aşağıdaki şekilde istek atılmalıdır.
Kullanıcı hesabı başarılı yaratıldıktan sonra, login işlemine geçebiliriz.
Go ile ufak bir restful api geliştirmek bu kadar hızlı ve kolay! Sorularınız olursa bu bağlantıya tıklayarak bize ulaşabilirsiniz.
Web & Mobil Geliştirme Takım Lideri
U-ETDS Zorunluluğuna Uyum Rehberi: SAP TM ve Non-SAP Entegrasyon Çözümleri
Ulaştırma sektöründe dijital dönüşümün önemli bir adımı olan Ulaştırma Elektronik Takip ve Denetim Sistemi (U-ETDS), firmalar için...
SAP Integration Suite’de Process Direct ile Çoklu Süreç Yönetimi
Integration Suite platformu üzerinde birden fazla sürecinin aynı anda yönetilmesi gerekebilir. Bu yazıda, çoklu süreç yönetiminin nasıl...
SAP EWM Nedir? SAP EWM Danışmanlığı Nedir?
SAP Extended Warehouse Management (EWM) yani SAP Genişletilmiş Depo Yönetimi, işletmelerin depodaki envanterlerini verimli bir şekilde...
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...
e-Fatura Hakkında Sıkça Sorulan Sorular
Teknolojik gelişmelerin ve dijital dönüşümün etkisiyle, şirketler için zaman maliyeti yüksek olan birçok iş artık çok daha hızlı ve...
SAP INTEGRATION SUITE’de Veritabanı Entegrasyonu Nasıl Yapılır?
Bu blog yazımızda SAP Integration Suite platformunda bir veritabanı entegrasyonu nasıl yapılır bundan bahsedeceğim.İçeriğimizi incelemeden...
SAP MII Enerji İzleme ve Analiz Nedir?
Bir ürünün en büyük maliyetlerinden biri üretim aşamasında harcanan enerji maliyetidir. İşletmeler, rekabet gücünü korumak ve için...
SAP Analytics Cloud (SAC) Nedir?
Günümüzde gelişen teknoloji sonucu artan veriler ile yönetimi zorlaşan veri yığınları oluşmaya başladı. Bu verileri anlamlı hale...
SAP Integration Suite ile EDI
SAP Integration Suite sayesinde, şirketler EDI’yi kurumsal sistemleri arasında etkin bir şekilde kullanmanın zorluğunu ve zaman alan yönetim...
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.