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
Express.js & MongoDB ile Pooling Application
Express Nedir? Express, Node.js içerisinde web uygulamalarının daha kolay ve hızlı bir şekilde geliştirilmesini sağlamak amacıyla...
ABAP on Cloud Nedir?
SAP, iş dünyasında dijital dönüşümü hızlandırmak için sürekli yenilikler yapmakta. Bu yeniliklerden biri de ABAP on Cloud platformu....
SAP Cloud Application Programming Model (SAP CAP) Nedir?
SAP Cloud Application Programming Model (CAP), SAP Business Technology'nin (SAP BTP) bulut ortamında modern iş uygulamaları geliştirmek için...
e-Arşiv Fatura Sorgulama Nedir? Nasıl Gerçekleştirilir?
Dijital dönüşüme ister gönüllü ister zorunlu olarak geçmiş olun, alıcılarınız manuel yöntemleri kullanmakta ısrarcıysa süreciniz...
SAP Spartacus Nedir?
Hybris Nedir?Hybris, kurumsal çok kanallı ve ürün içerik yönetimi yazılımı satan bir Alman şirketidir. SAP SE'nin bir yan kuruluşu olan...
CarrefourSA EDI Konuşuyor
CarrefourSA, Türkiye'nin önde gelen perakende zincirlerinden biri olarak, iş süreçlerini optimize etmek ve operasyonel verimliliği artırmak...
Doğal Dil İşleme Nedir?
Doğal Dil İşleme (Natural Language Processing) Dil, insanlar arasında anlaşmayı sağlayan bir iletişim aracıdır. Sosyal yaşamın en büyük...
5 Adımda SAP e-Beyanname Çözümü
Dijital dönüşümün yükselişiyle özellikle finans ve muhasebe departmanlarının operasyonel iş yükünü hafifletecek birçok uygulama ve...
E-Muhasebe Fişi Kılavuzunda Son Gelişmeler
Koronavirüs sebebiyle yaşadığımız pandemi döneminde, dijital dönüşüm çözümleri sayesinde muhasebe işlemlerinin büyük bir kısmının...
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.