Barcha qo'llanmalar

Golang'da REST API qanday yaratiladi (batafsil qo'llanma)

Golang'da REST API yaratmoqchimisiz? API'ingizni sozlash, kodini yozish va deploy qilish uchun best practice'lar va tushunarli misollar bilan to'ldirilgan batafsil qo'llanmamizga amal qiling.

Muallif: William Imoh

Golang'da REST API qanday yaratiladi

Go'da REST API yaratish yuqori unumdorlik, deploy qilish qulayligi va scalability'ni o'zida birlashtiradi. Go'ning samarali runtime'i va built-in concurrency modeli uni katta hajmdagi so'rovlarni qayta ishlaydigan va shu bilan birga past latency'ni saqlab qoladigan ilovalar yaratish uchun ideal tanlovga aylantiradi. Mustahkam standart kutubxonasi va qo'llab-quvvatlovchi ekotizimi tufayli ilovalaringizga REST funksionalligini tezda qo'shishingiz mumkin.

2009-yilda rasmiy chiqarilganidan beri Go keng ommalashdi va kichik ko'lamdagi tizimlardan tortib mission-critical tizimlargacha bo'lgan ilovalarni yaratish uchun asosiy texnologiyaga aylanib bormoqda. Soha yetakchilari undan Docker va Kubernetes kabi containerization va orchestration vositalarini yaratishda foydalanganidan so'ng uning ommabopligi yanada keskin oshdi. Kichik servis yoki mission-critical ilova yaratayotganingizdan qat'i nazar, Go REST API ishlab chiqish uchun kuchli poydevor beradi.

Ushbu afzalliklarni hisobga olib, keling, Go imkoniyatlarini amalda qo'llab ko'ramiz. Bu qo'llanmada siz create, read, update va delete (CRUD) funksionalligini qo'llab-quvvatlaydigan, kitoblar hisobini yurituvchi oddiy boshqaruv API'sini yaratasiz. Shuningdek, bu funksiyalar kutilganidek ishlashiga ishonch hosil qilish uchun ularga unit test'lar yozasiz, API'larni himoyalash uchun authentication'ni joriy qilasiz va to'liq hujjatlar tayyorlaysiz.

Keling, ilovani yaratishni boshlaymiz.

1-qadam: Loyihani sozlash

Boshlash uchun terminal yordamida loyiha papkasini yarating.

mkdir go_book_api && cd go_book_api

Bu buyruqlar go_book_api nomli papka yaratadi va unga o'tadi.

Keyin loyiha ichida Go module'ni initialize qiling.

go mod init go_book_api

Bu buyruq loyiha dependency'larini kuzatib borish uchun go.mod faylini yaratadi.

Nihoyat, kerakli dependency'larni o'rnating.

go get -u github.com/gin-gonic/gin
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres
go get -u gorm.io/driver/sqlite
go get -u github.com/joho/godotenv

Keling, bu dependency'lar nima ekanini va ular nima uchun ishlatilishini ko'rib chiqamiz:

Gin — veb-ilovalar yaratish uchun freymvork.

Gorm — Go'ga asoslangan Object Relational Mapper (ORM). ORM'lar ma'lumotlar bazasidagi ma'lumotlarga query yuborish va ularni o'zgartirishni osonlashtiradi. O'rnatish paytida Postgres va SQLite driver'lari ham qo'shiladi. Ilova ma'lumotlari bilan ishlash uchun Postgres driver'idan foydalanasiz, SQLite esa unit test'lar yozishda ma'lumotlar bazasi bilan o'zaro ta'sirni mock qiladi.

godotenv — environment variable'larni yuklash uchun kutubxona.

Loyiha ma'lumotlar bazasini sozlash API ma'lumotlaringizni saqlash uchun loyihangiz ma'lumotlar bazasini Neon'da sozlashingiz kerak. Neon — boshlash uchun saxiy bepul tarifga ega, to'liq boshqariladigan serverless PostgreSQL ma'lumotlar bazasi.

Ma'lumotlar bazasini sozlash uchun Neon console'ga kiring va loyiha yarating.

Loyiha yaratish

Loyiha muvaffaqiyatli yaratilgach, ma'lumotlar bazasi URL'ini nusxalab oling.

Ma'lumotlar bazasi URL'ini nusxalash

Buning o'rniga istalgan managed Postgres servisi yoki lokal Postgres instance'idan ham foydalanishingiz mumkin.

Va nihoyat, loyihangiz papkasida .env faylini yarating va unga nusxalangan ma'lumotlar bazasi URL'ini qo'shing.

DB_URL=<REPLACE THIS WITH YOUR DATABASE URL>

2-qadam: Loyiha tuzilmasini yaratish

Go loyihangizni qanday tuzishingizni majburlamasa-da, Go bilan ilova yaratishda yaxshi tuzilmaga ega bo'lish juda muhim. Bu codebase'ingizni qo'llab-quvvatlashni osonlashtiradi va boshqalar uchun tushunarli qiladi.

Loyihangizni tuzish uchun api, cmd va tests papkalarini yarating.

api — API ishlab chiqishga oid fayllarni tartibga solish uchun. cmd — ilovaning entry point'ini joylashtirish uchun ishlatiladi. Bu Go hamjamiyatida qabul qilingan konvensiya. tests — unit test'larni tartibga solish uchun.

3-qadam: Kitoblar hisobini yurituvchi ilova uchun CRUD amallarini yozish

Ilova ma'lumotlari va javoblarining tuzilmasini belgilash uchun api/model.go faylini yarating.

package api

import "github.com/gin-gonic/gin"

type Book struct {
	ID     uint   `json:"id" gorm:"primaryKey"`
	Title  string `json:"title"`
	Author string `json:"author"`
	Year   int    `json:"year"`
}

type JsonResponse struct {
	Status  int    `json:"status"`
	Message string `json:"message"`
	Data    any    `json:"data"`
}

func ResponseJSON(c *gin.Context, status int, message string, data any) {
	response := JsonResponse{
		Status:  status,
		Message: message,
		Data:    data,
	}

	c.JSON(status, response)
}

Yuqoridagi kod parchasi struct tag'lar (masalan, json:"title") yordamida kerakli property'larga ega Book va JsonResponse struct'larini hamda API javoblarini boshqarish uchun ResponseJSON yordamchi funksiyasini yaratadi.

Struct tag JSON nomlash konvensiyasiga mos keladigan javoblarni yuborish imkonini beradi.

Yangi kitob yaratish

Yangi kitob yaratuvchi handler funksiyasini yozish uchun ilovaning business logic'ini o'z ichiga oladigan api/handlers.go faylini yarating. Business logic'ni data model'dan ajratish muhim, chunki bu scalable, qo'llab-quvvatlashga qulay va test qilish oson bo'lgan kod yozishga yordam beradi.

package api

import (
	"github.com/gin-gonic/gin"
	"github.com/joho/godotenv"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"log"
	"net/http"
	"os"
)

var DB *gorm.DB

func InitDB() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}

	dsn := os.Getenv("DB_URL")
	DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}

	// migrate the schema
	if err := DB.AutoMigrate(&Book{}); err != nil {
		log.Fatal("Failed to migrate schema:", err)
	}
}

func CreateBook(c *gin.Context) {
	var book Book

	//bind the request body
	if err := c.ShouldBindJSON(&book); err != nil {
		ResponseJSON(c, http.StatusBadRequest, "Invalid input", nil)
		return
	}
	DB.Create(&book)
	ResponseJSON(c, http.StatusCreated, "Book created successfully", book)
}

Kod parchasi ma'lumotlar bazasi ulanishini saqlaydigan global DB o'zgaruvchisini e'lon qiladi, so'ngra u InitDB funksiyasi ichida initialize qilinadi. InitDB funksiyasi ma'lumotlar bazasiga ulanish uchun environment variable'ni yuklaydi va avtomatik schema migration'ni bajaradi. Nihoyat, yangi kitob yaratish, xatolarni qayta ishlash va ResponseJSON yordamchisi orqali tegishli javobni qaytarish uchun DB o'zgaruvchisidan foydalanadigan CreateBook handler'ini yarating.

Funksiya, type yoki o'zgaruvchi bosh harf bilan boshlanadigan nom bilan e'lon qilinsa, u export qilingan bo'ladi. Bu u boshqa package'larga ko'rinishini va ular undan foydalana olishini anglatadi.

Kitoblar ro'yxatini olish

Kitoblar ro'yxatini olish uchun ma'lumotlar bazasidagi mavjud kitoblar ro'yxatini DB o'zgaruvchisi orqali oladigan GetBooks handler'ini yarating.

func GetBooks(c *gin.Context) {
	var books []Book
	DB.Find(&books)
	ResponseJSON(c, http.StatusOK, "Books retrieved successfully", books)
}

Bitta kitobni olish

Kitobni olish uchun DB o'zgaruvchisi orqali ko'rsatilgan ID'ga mos kitobni oladigan va tegishli javobni qaytaradigan GetBook handler'ini yarating.

func GetBook(c *gin.Context) {
	var book Book
	if err := DB.First(&book, c.Param("id")).Error; err != nil {
		ResponseJSON(c, http.StatusNotFound, "Book not found", nil)
		return
	}
	ResponseJSON(c, http.StatusOK, "Book retrieved successfully", book)
}

Kitobni yangilash

Kitobni yangilash uchun DB o'zgaruvchisi orqali ko'rsatilgan ID'ga mos kitobni oladigan, uni yangilaydigan va tegishli javobni qaytaradigan UpdateBook handler'ini yarating.

func UpdateBook(c *gin.Context) {
	var book Book
	if err := DB.First(&book, c.Param("id")).Error; err != nil {
		ResponseJSON(c, http.StatusNotFound, "Book not found", nil)
		return
	}

	// bind the request body
	if err := c.ShouldBindJSON(&book); err != nil {
		ResponseJSON(c, http.StatusBadRequest, "Invalid input", nil)
		return
	}

	DB.Save(&book)
	ResponseJSON(c, http.StatusOK, "Book updated successfully", book)
}

Kitobni o'chirish

Kitobni o'chirish uchun DB o'zgaruvchisi orqali ko'rsatilgan ID'ga mos kitobni oladigan, uni o'chiradigan va tegishli javobni qaytaradigan DeleteBook handler'ini yarating.

func DeleteBook(c *gin.Context) {
	var book Book
	if err := DB.Delete(&book, c.Param("id")).Error; err != nil {
		ResponseJSON(c, http.StatusNotFound, "Book not found", nil)
		return
	}
	ResponseJSON(c, http.StatusOK, "Book deleted successfully", nil)
}

Hammasini birlashtirish

Handler'lar tayyor bo'lgach, ilovaning entry point'ini yaratishingiz va ilova route'larini belgilashingiz kerak. Buning uchun cmd/main.go faylini yarating va unga quyidagi kod parchasini qo'shing:

package main

import (
	"github.com/gin-gonic/gin"
	"go_book_api/api"
)

func main() {
	api.InitDB()
	r := gin.Default()

	//routes
	r.POST("/book", api.CreateBook)
	r.GET("/books", api.GetBooks)
	r.GET("/book/:id", api.GetBook)
	r.PUT("/book/:id", api.UpdateBook)
	r.DELETE("/book/:id", api.DeleteBook)

	r.Run(":8080")
}

Yuqoridagi kod parchasi ma'lumotlar bazasini initialize qiladigan, route'larni ularga tegishli handler'lar bilan belgilaydigan va ilovani 8080 port'ida ishga tushiradigan main funksiyasini yaratadi.

4-qadam: REST API'larni test qilish

API'larni test qilish uchun sizga Postman yoki o'zingiz tanlagan istalgan API test qilish ilovasi kerak bo'ladi.

Terminalingizda quyidagi buyruqni ishga tushirib, development server'ni ishga tushiring.

go run cmd/main.go

So'ngra, quyida ko'rsatilganidek, endpoint'larni sinab ko'rish uchun o'zingiz tanlagan API test qilish client'idan foydalaning:

Kitob yaratish endpoint'i

Yangi kitob yaratish uchun POST method'idan foydalaning va kerakli ma'lumotlarni uzating.

curl --location 'localhost:8080/book' \
     --header 'Content-Type: application/json' \
     --data '{
       "title": "Go by Example: Programmer'\''s guide to idiomatic and testable code",
       "author": "Inanc Gumus",
       "year": 2021
     }'

Kitob yaratish

Kitoblar ro'yxatini olish

Ma'lumotlar bazasidagi mavjud kitoblar ro'yxatini olish uchun GET method'idan foydalaning.

curl --location 'localhost:8080/books'

Kitoblar ro'yxati

Kitobni olish endpoint'i

Mavjud kitob tafsilotlarini olish uchun GET method'idan foydalaning va ID'ni qo'shing.

curl --location 'localhost:8080/book/3'

Kitobni olish

Kitobni yangilash endpoint'i

Mavjud kitobni yangilash uchun PUT method'idan foydalaning hamda ID va yangi ma'lumotlarni ko'rsating.

curl --location --request PUT 'localhost:8080/book/3' \
    --header 'Content-Type: application/json' \
    --data '{
        "title": "Go by Example: Programmer'\''s guide to idiomatic and testable code - Updated",
        "author": "Inanc Gumus",
        "year": 2023
    }'

Yangilangan kitob

Kitobni o'chirish

Avval yaratilgan kitob ID'si bilan DELETE so'rovini yuboring.

curl --location --request DELETE 'localhost:8080/book/3'

Kitobni o'chirish.

Eng so'nggi o'zgarishlarni ko'rish uchun Neon ma'lumotlar bazangizga query yuborishingiz ham mumkin.

Neon ma'lumotlar bazasi

5-qadam: API handler funksiyalarini unit test qilish

Go o'zining testing package'i bilan birga keladi va u unit test'lar, integration test'lar, functional test'lar hamda end-to-end test'lar yozishni osonlashtiradi. Boshlash uchun tests/main_test.go faylini yarating va unga quyidagi kod parchasini qo'shing:

package tests

import (
	"bytes"
	"encoding/json"
	"github.com/gin-gonic/gin"
	"go_book_api/api"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"
)

func setupTestDB() {
	var err error
	api.DB, err = gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	if err != nil {
		panic("failed to connect test database")
	}
	api.DB.AutoMigrate(&api.Book{})
}

func addBook() api.Book {
	book := api.Book{Title: "Go Programming", Author: "John Doe", Year: 2023}
	api.DB.Create(&book)
	return book
}

Yuqoridagi kod parchasi setupTestDB va addBook yordamchi funksiyalarini yaratadi. Ular mos ravishda faqat test maqsadlari uchun yangi SQLite in-memory ma'lumotlar bazasini (:memory: ) initialize qiladi va test ma'lumotlar bazasiga yangi kitob yozuvini qo'shadi.

Test fayli nomini _test.go bilan tugatish Go ekotizimidagi standart nomlash konvensiyasidir.

Yangi kitob yaratish uchun unit test

Gin yordamida mock HTTP server'ni sozlaydigan, kitob yaratish uchun POST so'rovini yuboradigan hamda javob kutilgan natijaga va CreateBook handler'ida belgilangan HTTP status kodiga mos kelishini tekshiradigan TestCreateBook test funksiyasini yarating.

func TestCreateBook(t *testing.T) {
	setupTestDB()
	router := gin.Default()
	router.POST("/book", api.CreateBook)

	book := api.Book{
		Title: "Demo Book name", Author: "Demo Author name", Year: 2021,
	}

	jsonValue, _ := json.Marshal(book)
	req, _ := http.NewRequest("POST", "/book", bytes.NewBuffer(jsonValue))

	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if status := w.Code; status != http.StatusCreated {
		t.Errorf("Expected status %d, got %d", http.StatusCreated, status)
	}
	var response api.JsonResponse
	json.NewDecoder(w.Body).Decode(&response)

	if response.Data == nil {
		t.Errorf("Expected book data, got nil")
	}
}

Kitoblar ro'yxatini olish uchun unit test

TestCreateBook test funksiyasiga o'xshab, GET so'rovi orqali kitoblar ro'yxatini olish bo'yicha GetBooks handler funksionalligini test qiladigan TestGetBooks funksiyasini yarating.

func TestGetBooks(t *testing.T) {
	setupTestDB()
	addBook()
	router := gin.Default()
	router.GET("/books", api.GetBooks)

	req, _ := http.NewRequest("GET", "/books", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if status := w.Code; status != http.StatusOK {
		t.Errorf("Expected status %d, got %d", http.StatusOK, status)
	}

	var response api.JsonResponse
	json.NewDecoder(w.Body).Decode(&response)

	if len(response.Data.([]interface{})) == 0 {
		t.Errorf("Expected non-empty books list")
	}
}

Bitta kitobni olish uchun unit test

GET so'rovi orqali ko'rsatilgan ID'ga mos bitta kitobni olish bo'yicha GetBook handler funksionalligini test qiladigan TestGetBook test funksiyasini yarating.

func TestGetBook(t *testing.T) {
	setupTestDB()
	book := addBook()
	router := gin.Default()
	router.GET("/book/:id", api.GetBook)

	req, _ := http.NewRequest("GET", "/book/"+strconv.Itoa(int(book.ID)), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if status := w.Code; status != http.StatusOK {
		t.Errorf("Expected status %d, got %d", http.StatusOK, status)
	}

	var response api.JsonResponse
	json.NewDecoder(w.Body).Decode(&response)

	if response.Data == nil || response.Data.(map[string]interface{})["id"] != float64(book.ID) {
		t.Errorf("Expected book ID %d, got nil or wrong ID", book.ID)
	}
}

Kitobni yangilash uchun unit test

PUT so'rovi orqali mavjud kitobni yangilash bo'yicha UpdateBook handler funksionalligini test qiladigan TestUpdateBook test funksiyasini yarating.

func TestUpdateBook(t *testing.T) {
	setupTestDB()
	book := addBook()
	router := gin.Default()
	router.PUT("/book/:id", api.UpdateBook)

	updateBook := api.Book{
		Title: "Advanced Go Programming", Author: "Demo Author name", Year: 2021,
	}
	jsonValue, _ := json.Marshal(updateBook)

	req, _ := http.NewRequest("PUT", "/book/"+strconv.Itoa(int(book.ID)), bytes.NewBuffer(jsonValue))
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if status := w.Code; status != http.StatusOK {
		t.Errorf("Expected status %d, got %d", http.StatusOK, status)
	}

	var response api.JsonResponse
	json.NewDecoder(w.Body).Decode(&response)

	if response.Data == nil || response.Data.(map[string]interface{})["title"] != "Advanced Go Programming" {
		t.Errorf("Expected updated book title 'Advanced Go Programming', got %v", response.Data)
	}
}

Kitobni o'chirish uchun unit test

DELETE so'rovi orqali mavjud kitobni o'chirish bo'yicha DeleteBook handler funksionalligini test qiladigan TestDeleteBook test funksiyasini yarating.

func TestDeleteBook(t *testing.T) {
	setupTestDB()
	book := addBook()
	router := gin.Default()
	router.DELETE("/book/:id", api.DeleteBook)

	req, _ := http.NewRequest("DELETE", "/book/"+strconv.Itoa(int(book.ID)), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if status := w.Code; status != http.StatusOK {
		t.Errorf("Expected status %d, got %d", http.StatusOK, status)
	}

	var response api.JsonResponse
	json.NewDecoder(w.Body).Decode(&response)

	if response.Message != "Book deleted successfully" {
		t.Errorf("Expected delete message 'Book deleted successfully', got %v", response.Message)
	}

	//verify that the book was deleted
	var deletedBook api.Book
	result := api.DB.First(&deletedBook, book.ID)
	if result.Error == nil {
		t.Errorf("Expected book to be deleted, but it still exists")
	}
}

Nihoyat, quyidagi buyruq yordamida test'ingizni development muhitida ishga tushirishingiz mumkin:

GIN_MODE=release go test tests/main_test.go -v

GIN_MODE=release — Gin'dagi buyruq bo'lib, u Gin'ga production muhitini taqlid qilishni va test'ni optimallashtirilgan rejimda ishga tushirishni bildiradi.

Test'ni ishga tushirish

REST API'larga authentication qo'shish

Mustahkam API yaratishning ajralmas qismi — authentication mexanizmi. U API'ingizdan foydalanayotgan foydalanuvchi yoki tizimning shaxsini tasdiqlaydi. API'ingizda authentication sozlangan bo'lsa, siz:

  • Maxfiy resurslar yoki ma'lumotlarga ruxsatsiz kirishning oldini olasiz.
  • Kirish darajasini nazorat qilasiz.
  • API'ga kim kirgani va qanday amallarni bajarganini kuzatib, log'ga yozib borasiz.
  • Authenticated foydalanuvchilarga moslashtirilgan javoblar yoki tajriba taqdim etasiz.

Go'da authentication'ni quyidagi usullardan biri bilan amalga oshirishingiz mumkin:

  • JSON Web Tokens (JWT).
  • OAuth 2.0.
  • Session-based authentication.
  • API key'lar.

Bu loyihada API'ni himoyalash uchun JWT (JSON Web Tokens)'dan foydalanasiz. Jarayon foydalanuvchi o'z credential'larini, masalan, username va parolini taqdim etishidan boshlanadi. Keyin tizim bu credential'larni tekshiradi va token beradi, foydalanuvchi esa undan himoyalangan endpoint'larga kirish uchun foydalanishi mumkin.

Boshlash uchun JWT package'ini o'rnating.

go get -u github.com/golang-jwt/jwt/v5

Middleware yaratish

Middleware'lar — HTTP so'rovlarini yakuniy route handler'ga yetib borishidan oldin yoki javob handler'dan chiqqanidan keyin ushlab qoladigan va qayta ishlaydigan funksiyalar yoki modullar. Bu holatda siz istalgan endpoint'ga murojaat qilinishidan oldin so'rovda to'g'ri token borligini tekshirmoqchisiz.

Buning uchun avval .env faylingizga secret token qo'shing. Bu token token'larning xavfsizligi, haqiqiyligi va yaxlitligini ta'minlaydi.

SECRET_TOKEN=<REPLACE WITH A RANDOM LONG STRING>

Keyin api papkasi ichida middleware.go faylini yarating va unga quyidagi kod parchasini qo'shing:

package api

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"net/http"
	"os"
)

// Secret key for signing JWT
var jwtSecret = []byte(os.Getenv("SECRET_TOKEN"))

func JWTAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		tokenString := c.GetHeader("Authorization")
		if tokenString == "" {
			ResponseJSON(c, http.StatusUnauthorized, "Authorization token required", nil)
			c.Abort()
			return
		}
		// parse and validate the token
		_, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
			// Validate the signing method
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
			}
			return jwtSecret, nil
		})
		if err != nil {
			ResponseJSON(c, http.StatusUnauthorized, "Invalid token", nil)
			c.Abort()
			return
		}
		// Token is valid, proceed to the next handler
		c.Next()
	}
}

Yuqoridagi kod parchasi JWT package'idan foydalanib kiruvchi so'rovlarni ushlab qoladigan va so'rovni handler'ga o'tkazishdan oldin ularning JWT token'larini validate qiladigan JWTAuthMiddleware funksiyasini yaratadi.

Keyin handlers.go fayliga standart username va parol asosida JWT token yaratadigan GenerateJWT funksiyasini qo'shing.

package api

import (
	//other imports
	"github.com/golang-jwt/jwt/v5"
)

func GenerateJWT(c *gin.Context) {
	var loginRequest LoginRequest
	if err := c.ShouldBindJSON(&loginRequest); err != nil {
		ResponseJSON(c, http.StatusBadRequest, "Invalid request payload", nil)
		return
	}
	if loginRequest.Username != "admin" || loginRequest.Password != "password" {
		ResponseJSON(c, http.StatusUnauthorized, "Invalid credentials", nil)
		return
	}
	expirationTime := time.Now().Add(15 * time.Minute)
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"exp": expirationTime.Unix(),
	})
	// Sign the token
	tokenString, err := token.SignedString(jwtSecret)
	if err != nil {
		ResponseJSON(c, http.StatusInternalServerError, "Could not generate token", nil)
		return
	}
	ResponseJSON(c, http.StatusOK, "Token generated successfully", gin.H{"token": tokenString})
}

Real ilovada token yaratish uchun ishlatiladigan credential'lar standart emas, balki har bir foydalanuvchiga xos bo'ladi.

Va nihoyat, token yaratish uchun public route qo'shish va API route'larini middleware yordamida himoyalash uchun cmd papkasi ichidagi main.go faylini yangilang.

package main

import (
	"github.com/gin-gonic/gin"
	"go_book_api/api"
)

func main() {
	api.InitDB()
	r := gin.Default()

	// Public routes
	r.POST("/token", api.GenerateJWT)

	// protected routes
	protected := r.Group("/", api.JWTAuthMiddleware())
	{
		protected.POST("/book", api.CreateBook)
		protected.GET("/books", api.GetBooks)
		protected.GET("/book/:id", api.GetBook)
		protected.PUT("/book/:id", api.UpdateBook)
		protected.DELETE("/book/:id", api.DeleteBook)
	}

	r.Run(":8080")
}

REST API'ni test qilish

Server'ni qayta ishga tushirib, token'siz istalgan endpoint'ga so'rov yuborsangiz, unauthorized javobini olasiz.

curl --location 'localhost:8080/books'

Token'siz so'rov

API'ni token bilan test qilish uchun token yaratish endpoint'iga so'rov yuboring.

curl --location 'localhost:8080/token' \
    --header 'Content-Type: application/json' \
    --data '{
      "username": "admin",
      "password": "password"
    }'

So'ngra yaratilgan token yordamida so'rov yuboring.

curl --location 'localhost:8080/books' \
     --header 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzM3MDU3Njl9.ULspm6GR9Q0zqZWHifdFEeLZqgtw7k2FDDhSOpwcw4U'

Unit test'ni yangilash

API route'i endi himoyalangani sababli, authentication mexanizmini hisobga olish uchun unit test'ni ham yangilashingiz kerak.

package tests

import (
	// other imports
	"github.com/golang-jwt/jwt/v5"
)

var jwtSecret = []byte(os.Getenv("SECRET_TOKEN"))

func setupTestDB() {
	// setupTestDB goes here
}

func addBook() api.Book {
	// add book code goes here
}

func generateValidToken() string {
	expirationTime := time.Now().Add(15 * time.Minute)
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"exp": expirationTime.Unix(),
	})
	tokenString, _ := token.SignedString(jwtSecret)
	return tokenString
}

func TestGenerateJWT(t *testing.T) {
	router := gin.Default()
	router.POST("/token", api.GenerateJWT)

	loginRequest := map[string]string{
		"username": "admin",
		"password": "password",
	}

	jsonValue, _ := json.Marshal(loginRequest)
	req, _ := http.NewRequest("POST", "/token", bytes.NewBuffer(jsonValue))

	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if status := w.Code; status != http.StatusOK {
		t.Errorf("Expected status %d, got %d", http.StatusOK, status)
	}

	var response api.JsonResponse
	json.NewDecoder(w.Body).Decode(&response)

	if response.Data == nil || response.Data.(map[string]interface{})["token"] == "" {
		t.Errorf("Expected token in response, got nil or empty")
	}
}

Yuqoridagi kod parchasi kerakli dependency'ni import qiladi hamda generateValidToken yordamchisini va TestGenerateJWT funksiyasini yaratadi: ular mos ravishda token yaratadi va token yaratish endpoint'ini test qiladi.

Keyin generateValidToken yordamchi funksiyasidan foydalanib, har bir test uchun request obyektini o'zgartiring: route'ga authentication qo'shing, token yarating va uni so'rov header'iga qo'shing. Masalan, kitob yaratish test'i quyidagicha ko'rinadi:

func TestCreateBook(t *testing.T) {
	setupTestDB()
	router := gin.Default()
	protected := router.Group("/", api.JWTAuthMiddleware()) // add
	protected.POST("/book", api.CreateBook)                 // add

	token := generateValidToken() // add

	book := api.Book{
		Title: "Demo Book name", Author: "Demo Author name", Year: 2021,
	}
	jsonValue, _ := json.Marshal(book)

	req, _ := http.NewRequest("POST", "/book", bytes.NewBuffer(jsonValue))
	req.Header.Set("Authorization", token) // add

	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if status := w.Code; status != http.StatusCreated {
		t.Errorf("Expected status %d, got %d", http.StatusCreated, status)
	}

	var response api.JsonResponse
	json.NewDecoder(w.Body).Decode(&response)

	if response.Data == nil {
		t.Errorf("Expected book data, got nil")
	}
}

REST API'larga hujjatlar qo'shish

Hujjatlar API'laringizning ajralmas qismidir. Ular maqsadingizni yetkazishga hamda boshqalarga API'laringizni tushunish, qo'llab-quvvatlash va samarali ishlatishga yordam beradi. Hujjatlar quyidagi ko'rinishlarda bo'lishi mumkin:

  • Texnik hujjatlar — dasturchilarga API'lar, konfiguratsiyalar va tizim arxitekturasi bo'yicha yo'l-yo'riq beradi.
  • Foydalanuvchi qo'llanmasi — oxirgi foydalanuvchilarga mahsulotdan qanday foydalanishni tushunishga yordam beradi.
  • Ichki hujjatlar — ichki jarayonlar, workflow'lar va boshqa operatsion tafsilotlarni qayd etadi.
  • Kod izohlari — kodning muayyan qismlariga bevosita kod ichida tushuntirish beradi.

Go'da hujjatlarni yaratish, qo'llab-quvvatlash va ko'rsatish uchun turli vositalardan foydalanishingiz mumkin. Quyida ulardan eng ko'p ishlatiladiganlari keltirilgan:

  • GoDoc
  • pkg.go.dev
  • go doc
  • godocdown
  • Swagger/OpenAPI

Bu loyihada hujjatlar qo'shish uchun GoDoc'dan foydalanasiz.

Boshlash uchun avval operatsion tizimingizga mos ravishda Go versiyasini yangilang. Keyin quyidagi buyruq yordamida GoDoc package'ini o'rnating:

go get golang.org/x/tools/cmd/godoc

Keyin handlers.rs faylini o'zgartirib, har bir yordamchi funksiya va handler nima qilishini tavsiflovchi izohlar qo'shing.

/*
Package api provides RESTful handlers and middleware for managing a library of books.
It includes functionality for creating, reading, updating, and deleting books, as well as generating JWT tokens for authentication.
*/
package api

import (
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/joho/godotenv"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"log"
	"net/http"
	"os"
	"time"
)

var DB *gorm.DB

// InitDB initializes the database connection using environment variables.
// It loads the database configuration from a .env file and migrates the Book schema.
func InitDB() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	dsn := os.Getenv("DB_URL")
	DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	// Migrate the schema
	if err := DB.AutoMigrate(&Book{}); err != nil {
		log.Fatal("Failed to migrate schema:", err)
	}
}

// CreateBook handles the creation of a new book in the database.
// It expects a JSON payload with book details and responds with the created book.
func CreateBook(c *gin.Context) {
	var book Book
	if err := c.ShouldBindJSON(&book); err != nil {
		ResponseJSON(c, http.StatusBadRequest, "Invalid input", nil)
		return
	}
	DB.Create(&book)
	ResponseJSON(c, http.StatusCreated, "Book created successfully", book)
}

// GetBooks retrieves all books from the database.
// It responds with a list of books.
func GetBooks(c *gin.Context) {
	var books []Book
	DB.Find(&books)
	ResponseJSON(c, http.StatusOK, "Books retrieved successfully", books)
}

// other handlers goes below

Nihoyat, quyidagi buyruqni ishga tushirib hujjatlarni tekshirib ko'ring:

godoc -http=:6060

Brauzeringizni oching va hujjatlar URL'iga o'ting:

http://localhost:6060/pkg/go_book_api/api/

Book API hujjatlari

Keyingi qadamlar

Go'ning soddaligi, built-in concurrency'si, cloud-native imkoniyatlari va unumdorligi uni dasturchilar orasida mashhur tanlovga aylantiradi. O'sib borayotgan ekotizim va hamjamiyat yordami esa RESTful API'larni, shuningdek siz yaratmoqchi bo'lgan boshqa har qanday ilovani samarali yaratish va qo'llab-quvvatlash uchun kerakli vositalar va kutubxonalarga ega bo'lishingizni ta'minlaydi.

Gin freymvorkidan tashqari Go'da API yaratish uchun Echo, Revel va boshqa ko'plab mashhur veb-freymvorklar mavjud. Eng so'nggi o'zgarishlardan xabardor bo'lib turish uchun Go roadmap'ga qarang.

Yo'l xaritalariQo'llanmalarFAQYouTube

yo'lxaritaroadmap.shasosida

Hamjamiyat tomonidan yaratilgan yo'l xaritalari, eng yaxshi amaliyotlar, loyihalar, maqolalar, manbalar va yo'nalishlar — o'z yo'lingizni tanlash va kasbingizda o'sish uchun.

© yo'lxarita·Shartlar·Maxfiylik·

roadmap.sh

Barcha kontent roadmap.sh loyihasidan olingan (© Kamran Ahmed) va shaxsiy o'rganish uchun o'zbek tiliga tarjima qilinmoqda.

Qidiruv

Yo'l xaritalari, loyihalar va savollarni qidiring