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
Elektronik Veri Değişimi (EDI) Kılavuzu
Günümüzde işletmeler, satın alma siparişlerini, faturalarını, teklif taleplerini, kredi uygulamalarını ve daha birçok belge türünü...
SAP Signavio Faydaları Nelerdir?
Teknolojinin hızla geliştiği, sürekli değişimin ve dönüşümün olduğu bir dünyada yaşamaktayız. Bu değişimler, şirketlerin iş...
5 Adımda SAP Envanter Takip Çözümü
Şirketlerin envanter yönetimi konusundaki başarısı, kazancını ve büyüme hızını doğrudan etkilemektedir. Üretimin sistematik ve düzenli...
e-İrsaliye Hakkında Sıkça Sorulan Sorular
e-İrsaliye Nedir? e-İrsaliye, bir malın taşınması veya başka bir depoya sevk edilme sürecinde hazırlanması zorunlu tutulan irsaliye...
SAP Data Hub Nedir? Avantajları Nelerdir?
27 Eylül 2017 tarihinde yayınlananan SAP Data Hub; şirketlerin, çeşitli veri ortamlarında veri akışını hızlandırmasına ve...
SAP EWM’in Temel Özellikleri ve Fonksiyonları
Bir depo yöneticisi veya tedarik zinciri profesyoneliyseniz, bir depoyu yönetmenin karmaşık ve zaman alıcı bir görev olabileceğini...
e-Fatura ve e-Arşiv Fatura Arasındaki Farklar
e-Dönüşüm, Gelir İdaresi Başkanlığı tarafından teknolojiyi iş süreçlerine entegre etmek için başlatılan...
SAPUI5’te Veri Bağlama (Data Binding) Nedir?
Bu blog yazımızda SAPUI5’taki veri bağlama türleri nelerdir, hangi durumlarda hangi veri bağlama türünü tercih etmeliyiz gibi soruların...
Diff Analyzer’da Miktar Düzeltme
SAP Extended Warehouse Management (EWM), lojistik operasyonlarını yönetmek için kritik bir rol oynar ve doğru envanter yönetimi, işletmeler...
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.