GoDasturchi
Putting It All Together: Wiring the Full API

Putting It All Together: Wiring the Full API

Barchasini birlashtirish: to'liq API'ni ulash

Konstruktor har bir g'ishtni ALOHIDA-ALOHIDA yaxshi qurgan bo'lishi mumkin, lekin HAQIQIY BINO — ularning HAMMASI TO'G'RI TARTIBDA, BIR-BIRIGA MOS qo'yilganda paydo bo'ladi. Shu kursda 22 ta darsda ALOHIDA-ALOHIDA qurgan qismlarni (BookStore, validatsiya, xato konverti, middleware'lar) endi BITTA, YAXLIT ishlaydigan API'ga BIRLASHTIRAMIZ.

example.go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"sync"
)

// ---------- Xato konverti (7-dars) ----------

type ErrorBody struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

type ErrorResponse struct {
	Error ErrorBody `json:"error"`
}

func writeError(w http.ResponseWriter, status int, code, message string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(ErrorResponse{Error: ErrorBody{Code: code, Message: message}})
}

// ---------- BookStore (3-4-darslar) ----------

type Book struct {
	ID     string  `json:"id"`
	Title  string  `json:"title"`
	Author string  `json:"author"`
	Price  float64 `json:"price"`
}

type BookStore struct {
	mu     sync.Mutex
	books  map[string]*Book
	nextID int
}

func NewBookStore() *BookStore { return &BookStore{books: make(map[string]*Book)} }

func (s *BookStore) Create(title, author string, price float64) *Book {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.nextID++
	id := strconv.Itoa(s.nextID)
	book := &Book{ID: id, Title: title, Author: author, Price: price}
	s.books[id] = book
	return book
}

// ---------- Middleware (15, 19-darslar) ----------

var apiKeys = map[string]string{"key-abc": "Acme Corp"}

func requireAPIKey(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		key := r.Header.Get("X-API-Key")
		if _, ok := apiKeys[key]; !ok {
			writeError(w, http.StatusUnauthorized, "invalid_api_key", "API kalit noto'g'ri yoki yo'q")
			return
		}
		next(w, r)
	}
}

var accessLog []string

func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		accessLog = append(accessLog, r.Method+" "+r.URL.Path)
		next(w, r)
	}
}

// chain — middleware'larni CHAPDAN O'NGGA qo'llaydi: chain(h, A, B) => A(B(h))
func chain(h http.HandlerFunc, mws ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
	for i := len(mws) - 1; i >= 0; i-- {
		h = mws[i](h)
	}
	return h
}

// ---------- Validatsiya (5-6-darslar) ----------

func validateCreate(title string, price float64) string {
	if strings.TrimSpace(title) == "" {
		return "title bo'sh bo'lishi mumkin emas"
	}
	if price <= 0 {
		return "price musbat son bo'lishi kerak"
	}
	return ""
}

func main() {
	store := NewBookStore()

	createHandler := func(w http.ResponseWriter, r *http.Request) {
		var req struct {
			Title  string  `json:"title"`
			Author string  `json:"author"`
			Price  float64 `json:"price"`
		}
		json.NewDecoder(r.Body).Decode(&req)
		if msg := validateCreate(req.Title, req.Price); msg != "" {
			writeError(w, http.StatusUnprocessableEntity, "validation_failed", msg)
			return
		}
		book := store.Create(req.Title, req.Author, req.Price)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusCreated)
		json.NewEncoder(w).Encode(book)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /books", chain(createHandler, loggingMiddleware, requireAPIKey))

	// API kalitsiz yaratishga urinish
	req1 := httptest.NewRequest("POST", "/books", strings.NewReader(`{"title":"Go","author":"A. Karimov","price":45000}`))
	rec1 := httptest.NewRecorder()
	mux.ServeHTTP(rec1, req1)
	fmt.Println(rec1.Code)
	fmt.Print(rec1.Body.String())

	// TO'G'RI kalit bilan yaratish
	req2 := httptest.NewRequest("POST", "/books", strings.NewReader(`{"title":"Go bilan tanishuv","author":"A. Karimov","price":45000}`))
	req2.Header.Set("X-API-Key", "key-abc")
	rec2 := httptest.NewRecorder()
	mux.ServeHTTP(rec2, req2)
	fmt.Println(rec2.Code)
	fmt.Print(rec2.Body.String())
}

chain(createHandler, loggingMiddleware, requireAPIKey) — funksiya ICHIDA mws SLICE'i TESKARI tartibda qo'llanadi: avval requireAPIKey(createHandler) HOSIL bo'ladi, so'ng buning USTIGA loggingMiddleware O'RALADI. Natijada loggingMiddleware ENG TASHQI qatlam bo'ladi — ya'ni HAR BIR so'rov (hatto autentifikatsiyadan O'TMAGANLARI ham) LOGLANADI, requireAPIKey esa undan KEYIN, biznes mantiqdan OLDIN turadi.

Bu — BUTUN kurs davomida ALOHIDA-ALOHIDA o'rganilgan HAR BIR qism (xato konverti, saqlash, middleware, validatsiya) BIR JOYGA — main funksiyasiga — QANDAY YIG'ILISHINI ko'rsatadi. HAQIQIY loyihada bu "yig'ish" ODATDA main.go yoki router.go faylida, DASTUR ishga tushishida BIR MARTA sodir bo'ladi.

>_ Exercise

GET endpoint'ini ham qo'shib, TO'LIQ API oqimini sinang.

  • getHandler yozing: store.Get(r.PathValue("id")) orqali kitobni toping, topilmasa writeError bilan 404 ("not_found") qaytaring, topilsa JSON qilib qaytaring
  • BookStore'ga Get(id string) (*Book, bool) metodini qo'shing
  • "GET /books/{id}" endpoint'ini xuddi POST kabi chain(getHandler, loggingMiddleware, requireAPIKey) bilan ro'yxatdan o'tkazing
  • TO'G'RI kalit bilan kitobni yaratgandan KEYIN, o'sha kitobni GET qiling, SO'NG mavjud bo'lmagan "/books/99" ni GET qiling
  • oxirida accessLog uzunligini (nechta so'rov LOGLANGANINI) chop eting

Stuck? Reveal a hint to help you.

Hints (0/3)

Key Takeaway

Key Takeaway:

To'liq API — BookStore, validatsiya, xato konverti va middleware zanjirini (chain) BITTA joyda birlashtirib, HAR BIR endpoint uchun BIR XIL, IZCHIL qatlamlar to'plamini qo'llash orqali quriladi.

NEXT UP

Project Recap & Next Steps

OUTPUT

$ go run main.go
Kodingizni ishga tushiring

Putting It All Together: Wiring the Full API

Barchasini birlashtirish: to'liq API'ni ulash

Konstruktor har bir g'ishtni ALOHIDA-ALOHIDA yaxshi qurgan bo'lishi mumkin, lekin HAQIQIY BINO — ularning HAMMASI TO'G'RI TARTIBDA, BIR-BIRIGA MOS qo'yilganda paydo bo'ladi. Shu kursda 22 ta darsda ALOHIDA-ALOHIDA qurgan qismlarni (BookStore, validatsiya, xato konverti, middleware'lar) endi BITTA, YAXLIT ishlaydigan API'ga BIRLASHTIRAMIZ.

example.go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"sync"
)

// ---------- Xato konverti (7-dars) ----------

type ErrorBody struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

type ErrorResponse struct {
	Error ErrorBody `json:"error"`
}

func writeError(w http.ResponseWriter, status int, code, message string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(ErrorResponse{Error: ErrorBody{Code: code, Message: message}})
}

// ---------- BookStore (3-4-darslar) ----------

type Book struct {
	ID     string  `json:"id"`
	Title  string  `json:"title"`
	Author string  `json:"author"`
	Price  float64 `json:"price"`
}

type BookStore struct {
	mu     sync.Mutex
	books  map[string]*Book
	nextID int
}

func NewBookStore() *BookStore { return &BookStore{books: make(map[string]*Book)} }

func (s *BookStore) Create(title, author string, price float64) *Book {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.nextID++
	id := strconv.Itoa(s.nextID)
	book := &Book{ID: id, Title: title, Author: author, Price: price}
	s.books[id] = book
	return book
}

// ---------- Middleware (15, 19-darslar) ----------

var apiKeys = map[string]string{"key-abc": "Acme Corp"}

func requireAPIKey(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		key := r.Header.Get("X-API-Key")
		if _, ok := apiKeys[key]; !ok {
			writeError(w, http.StatusUnauthorized, "invalid_api_key", "API kalit noto'g'ri yoki yo'q")
			return
		}
		next(w, r)
	}
}

var accessLog []string

func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		accessLog = append(accessLog, r.Method+" "+r.URL.Path)
		next(w, r)
	}
}

// chain — middleware'larni CHAPDAN O'NGGA qo'llaydi: chain(h, A, B) => A(B(h))
func chain(h http.HandlerFunc, mws ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
	for i := len(mws) - 1; i >= 0; i-- {
		h = mws[i](h)
	}
	return h
}

// ---------- Validatsiya (5-6-darslar) ----------

func validateCreate(title string, price float64) string {
	if strings.TrimSpace(title) == "" {
		return "title bo'sh bo'lishi mumkin emas"
	}
	if price <= 0 {
		return "price musbat son bo'lishi kerak"
	}
	return ""
}

func main() {
	store := NewBookStore()

	createHandler := func(w http.ResponseWriter, r *http.Request) {
		var req struct {
			Title  string  `json:"title"`
			Author string  `json:"author"`
			Price  float64 `json:"price"`
		}
		json.NewDecoder(r.Body).Decode(&req)
		if msg := validateCreate(req.Title, req.Price); msg != "" {
			writeError(w, http.StatusUnprocessableEntity, "validation_failed", msg)
			return
		}
		book := store.Create(req.Title, req.Author, req.Price)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusCreated)
		json.NewEncoder(w).Encode(book)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /books", chain(createHandler, loggingMiddleware, requireAPIKey))

	// API kalitsiz yaratishga urinish
	req1 := httptest.NewRequest("POST", "/books", strings.NewReader(`{"title":"Go","author":"A. Karimov","price":45000}`))
	rec1 := httptest.NewRecorder()
	mux.ServeHTTP(rec1, req1)
	fmt.Println(rec1.Code)
	fmt.Print(rec1.Body.String())

	// TO'G'RI kalit bilan yaratish
	req2 := httptest.NewRequest("POST", "/books", strings.NewReader(`{"title":"Go bilan tanishuv","author":"A. Karimov","price":45000}`))
	req2.Header.Set("X-API-Key", "key-abc")
	rec2 := httptest.NewRecorder()
	mux.ServeHTTP(rec2, req2)
	fmt.Println(rec2.Code)
	fmt.Print(rec2.Body.String())
}

chain(createHandler, loggingMiddleware, requireAPIKey) — funksiya ICHIDA mws SLICE'i TESKARI tartibda qo'llanadi: avval requireAPIKey(createHandler) HOSIL bo'ladi, so'ng buning USTIGA loggingMiddleware O'RALADI. Natijada loggingMiddleware ENG TASHQI qatlam bo'ladi — ya'ni HAR BIR so'rov (hatto autentifikatsiyadan O'TMAGANLARI ham) LOGLANADI, requireAPIKey esa undan KEYIN, biznes mantiqdan OLDIN turadi.

Bu — BUTUN kurs davomida ALOHIDA-ALOHIDA o'rganilgan HAR BIR qism (xato konverti, saqlash, middleware, validatsiya) BIR JOYGA — main funksiyasiga — QANDAY YIG'ILISHINI ko'rsatadi. HAQIQIY loyihada bu "yig'ish" ODATDA main.go yoki router.go faylida, DASTUR ishga tushishida BIR MARTA sodir bo'ladi.

>_ Exercise

GET endpoint'ini ham qo'shib, TO'LIQ API oqimini sinang.

  • getHandler yozing: store.Get(r.PathValue("id")) orqali kitobni toping, topilmasa writeError bilan 404 ("not_found") qaytaring, topilsa JSON qilib qaytaring
  • BookStore'ga Get(id string) (*Book, bool) metodini qo'shing
  • "GET /books/{id}" endpoint'ini xuddi POST kabi chain(getHandler, loggingMiddleware, requireAPIKey) bilan ro'yxatdan o'tkazing
  • TO'G'RI kalit bilan kitobni yaratgandan KEYIN, o'sha kitobni GET qiling, SO'NG mavjud bo'lmagan "/books/99" ni GET qiling
  • oxirida accessLog uzunligini (nechta so'rov LOGLANGANINI) chop eting

Stuck? Reveal a hint to help you.

Hints (0/3)

Key Takeaway

Key Takeaway:

To'liq API — BookStore, validatsiya, xato konverti va middleware zanjirini (chain) BITTA joyda birlashtirib, HAR BIR endpoint uchun BIR XIL, IZCHIL qatlamlar to'plamini qo'llash orqali quriladi.

NEXT UP

Project Recap & Next Steps