
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/muxkomutunu ç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 komutunu çalıştırıyoruz.

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-gokomutunu çalıştırıyoruz.

Environment dosyamızı (.env) yükleyebilmek için joho/godotenv paketini kullanacağız. Paketi yüklemek için;
go get –u github.com/joho/godotenvkomutunu çalıştırıyoruz.
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
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.