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.
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.
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
$ go run main.go
Kodingizni ishga tushiring