Tarkibga o'tish

Subroutes

API kattalashgani sari marshrutlar soni ham oshadi. Ularning ko‘pchiligi umumiy prefiksga ega bo‘ladi:

/api/v1/books
/api/v1/books/{id}
/api/v1/users
/api/v1/users/{id}
/api/v1/admin/stats
/api/v1/admin/users/{id}/ban

Har bir marshrutda /api/v1 qismini takrorlash noqulay. Versiyani v2ga o‘zgartirish kerak bo‘lsa, barcha qatorlarni qidirib, tahrirlab chiqishga to‘g‘ri keladi. Bundan tashqari, /admin ostidagi barcha marshrutlarga bir xil himoya qo‘yish kerak.

Subrouter bu muammolarni hal qiladi. U umumiy prefiks yoki shartga ega marshrutlarni bitta guruhga birlashtiradi.

PathPrefix va Subrouter

r := mux.NewRouter()

api := r.PathPrefix("/api/v1").Subrouter()
api.HandleFunc("/books", listBooks).Methods(http.MethodGet)       // /api/v1/books
api.HandleFunc("/books/{id}", getBook).Methods(http.MethodGet)    // /api/v1/books/{id}
  • r.PathPrefix("/api/v1") — /api/v1 bilan boshlanadigan barcha yo‘llarga mos keladigan marshrut yaratadi.
  • .Subrouter() — shu marshrut ichida yangi router qaytaradi.
  • Subrouter’dagi marshrutlar prefiksga nisbatan yoziladi: /books, /api/v1/books emas.

Subrouter’lar ichma-ich joylashishi mumkin:

api := r.PathPrefix("/api/v1").Subrouter()
books := api.PathPrefix("/books").Subrouter()

books.HandleFunc("", listBooks).Methods(http.MethodGet)              // /api/v1/books
books.HandleFunc("/{id:[0-9]+}", getBook).Methods(http.MethodGet)    // /api/v1/books/{id}
books.HandleFunc("/{id:[0-9]+}/reviews", listReviews).Methods("GET") // /api/v1/books/{id}/reviews

books.HandleFunc("", ...) bo‘sh yo‘l bilan yozilgan. U prefiksning o‘ziga, ya’ni /api/v1/booksga mos keladi.

Resurslarni alohida funksiyalarga ajratish

Subrouter kodni tartibga solish uchun ham qulay. Har bir resurs o‘z marshrutlarini o‘zi ro‘yxatdan o‘tkazadi:

func (h *BookHandler) Register(r *mux.Router) {
    r.HandleFunc("", h.list).Methods(http.MethodGet)
    r.HandleFunc("", h.create).Methods(http.MethodPost)
    r.HandleFunc("/{id:[0-9]+}", h.get).Methods(http.MethodGet)
    r.HandleFunc("/{id:[0-9]+}", h.delete).Methods(http.MethodDelete)
}

func (h *UserHandler) Register(r *mux.Router) {
    r.HandleFunc("", h.list).Methods(http.MethodGet)
    r.HandleFunc("/{id:[0-9]+}", h.get).Methods(http.MethodGet)
}

main.go esa faqat prefikslarni belgilaydi:

api := r.PathPrefix("/api/v1").Subrouter()
bookHandler.Register(api.PathPrefix("/books").Subrouter())
userHandler.Register(api.PathPrefix("/users").Subrouter())

BookHandler o‘zi qaysi prefiks ostida joylashganini bilmaydi. Uni /api/v2/books yoki /library/books ostiga ko‘chirish uchun faqat main.go o‘zgartiriladi. Bu yondashuv clean architecture g‘oyalari bilan yaxshi uyg‘unlashadi.

API versiyalash

API ommaga ochilgach, uning javob formatini o‘zgartirish mavjud klientlarni buzadi. Masalan, mobil ilovaning eski versiyalari yangilanmagan bo‘lishi mumkin. Shuning uchun katta o‘zgarishlar yangi versiya sifatida chiqariladi, eski versiya esa ma’lum muddat ishlashda davom etadi:

v1 := r.PathPrefix("/api/v1").Subrouter()
v1.HandleFunc("/books/{id:[0-9]+}", getBookV1).Methods(http.MethodGet)

v2 := r.PathPrefix("/api/v2").Subrouter()
v2.HandleFunc("/books/{id:[0-9]+}", getBookV2).Methods(http.MethodGet)

Ikkala versiya bitta serverda yonma-yon ishlaydi. Ular bir xil service qatlamidan foydalanadi, faqat javob formatlari farq qiladi.

Guruhga middleware qo‘shish

Subrouter’ning eng foydali xususiyatlaridan biri — middleware faqat shu guruhga qo‘llanishi:

api := r.PathPrefix("/api/v1").Subrouter()
api.HandleFunc("/books", listBooks).Methods(http.MethodGet) // ochiq

admin := api.PathPrefix("/admin").Subrouter()
admin.Use(auth.Require, RequireRole(RoleAdmin))
admin.HandleFunc("/stats", stats).Methods(http.MethodGet)     // himoyalangan
admin.HandleFunc("/users", listUsers).Methods(http.MethodGet) // himoyalangan

admin guruhiga keyinchalik qo‘shilgan har bir marshrut avtomatik himoyalanadi. Middleware’lar bilan ishlash keyingi darsda batafsil ko‘riladi.

Boshqa shartlar bo‘yicha subrouter

PathPrefixdan tashqari subrouter’ni boshqa shartlar bo‘yicha ham yaratish mumkin.

Host bo‘yicha

apiHost := r.Host("api.example.uz").Subrouter()
apiHost.HandleFunc("/books", listBooks)

adminHost := r.Host("admin.example.uz").Subrouter()
adminHost.HandleFunc("/", dashboard)

Host ichida ham o‘zgaruvchi ishlatish mumkin. Bu, masalan, har bir mijozga alohida subdomen beriladigan xizmatlar uchun qulay:

tenant := r.Host("{tenant:[a-z0-9-]+}.example.uz").Subrouter()
tenant.HandleFunc("/books", func(w http.ResponseWriter, r *http.Request) {
    name := mux.Vars(r)["tenant"] // "maktab-5.example.uz" → "maktab-5"
    // ...
})

Sarlavha bo‘yicha

// Versiyani URL emas, sarlavha orqali tanlash
v2 := r.Headers("X-API-Version", "2").Subrouter()
v2.HandleFunc("/books/{id}", getBookV2)

r.HandleFunc("/books/{id}", getBookV1) // sarlavha bo‘lmasa

Versiyani URL’da ko‘rsatish (/api/v2) va sarlavhada yuborish — ikkalasi ham keng tarqalgan. URL’dagi versiya ko‘rinarli va brauzerda sinash oson. Shu sababli u ko‘proq qo‘llanadi.

Info

Subrouter’lar ham ro‘yxatdan o‘tkazilgan tartibda tekshiriladi. Yuqoridagi misolda r.Headers(...) subrouter’i umumiy marshrutdan oldin e’lon qilingan. Tartib teskari bo‘lsa, sarlavha bilan kelgan so‘rov ham birinchi mos kelgan getBookV1 handleriga tushib qoladi.

To‘liq misol

main.go
package main

import (
    "encoding/json"
    "log"
    "net/http"
    "strings"

    "github.com/gorilla/mux"
)

func writeJSON(w http.ResponseWriter, status int, v any) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(v)
}

// ---------- Kitoblar ----------

type BookHandler struct{}

func (h *BookHandler) Register(r *mux.Router) {
    r.HandleFunc("", h.list).Methods(http.MethodGet)
    r.HandleFunc("/{id:[0-9]+}", h.get).Methods(http.MethodGet)
    r.HandleFunc("/{id:[0-9]+}/reviews", h.reviews).Methods(http.MethodGet)
}

func (h *BookHandler) list(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusOK, map[string]any{"data": []string{"O‘tkan kunlar", "Sarob"}})
}

func (h *BookHandler) get(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusOK, map[string]any{"data": map[string]string{"id": mux.Vars(r)["id"]}})
}

func (h *BookHandler) reviews(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusOK, map[string]any{"book_id": mux.Vars(r)["id"], "data": []string{}})
}

// ---------- v2: boshqa javob formati ----------

func getBookV2(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusOK, map[string]any{
        "book": map[string]string{"id": mux.Vars(r)["id"]},
        "meta": map[string]string{"version": "2"},
    })
}

// ---------- Admin ----------

func requireAdminKey(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Namuna uchun soddalashtirilgan tekshiruv. Real loyihada JWT va rol tekshiriladi.
        if r.Header.Get("X-Admin-Key") != "maxfiy" {
            writeJSON(w, http.StatusForbidden, map[string]string{"error": "admin huquqi kerak"})
            return
        }
        next.ServeHTTP(w, r)
    })
}

func stats(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusOK, map[string]int{"books": 2, "users": 10})
}

// ---------- Router ----------

func newRouter() *mux.Router {
    r := mux.NewRouter()

    v1 := r.PathPrefix("/api/v1").Subrouter()
    (&BookHandler{}).Register(v1.PathPrefix("/books").Subrouter())

    admin := v1.PathPrefix("/admin").Subrouter()
    admin.Use(requireAdminKey)
    admin.HandleFunc("/stats", stats).Methods(http.MethodGet)

    v2 := r.PathPrefix("/api/v2").Subrouter()
    v2.HandleFunc("/books/{id:[0-9]+}", getBookV2).Methods(http.MethodGet)

    return r
}

func main() {
    r := newRouter()

    r.Walk(func(route *mux.Route, _ *mux.Router, _ []*mux.Route) error {
        tpl, err := route.GetPathTemplate()
        methods, _ := route.GetMethods()
        if err == nil && len(methods) > 0 {
            log.Printf("%-4s %s", strings.Join(methods, ","), tpl)
        }
        return nil
    })

    log.Fatal(http.ListenAndServe(":8080", r))
}

Server ishga tushganda marshrutlar ro‘yxati chiqadi:

2026/09/30 10:00:00 GET  /api/v1/books
2026/09/30 10:00:00 GET  /api/v1/books/{id:[0-9]+}
2026/09/30 10:00:00 GET  /api/v1/books/{id:[0-9]+}/reviews
2026/09/30 10:00:00 GET  /api/v1/admin/stats
2026/09/30 10:00:00 GET  /api/v2/books/{id:[0-9]+}

r.Walk subrouter’lar ichiga ham kiradi va to‘liq yo‘llarni ko‘rsatadi. if sharti faqat metodli marshrutlarni chiqaradi. PathPrefix yozuvlarining o‘zi ro‘yxatga tushmaydi.

curl http://localhost:8080/api/v1/books
curl http://localhost:8080/api/v1/books/7/reviews
curl http://localhost:8080/api/v2/books/7
curl http://localhost:8080/api/v1/admin/stats
curl -H "X-Admin-Key: maxfiy" http://localhost:8080/api/v1/admin/stats
{"data":["O‘tkan kunlar","Sarob"]}
{"book_id":"7","data":[]}
{"book":{"id":"7"},"meta":{"version":"2"}}
{"error":"admin huquqi kerak"}
{"books":2,"users":10}

Admin middleware’i faqat /api/v1/admin guruhiga qo‘llandi. Kitoblar endpointlari esa ochiq qoldi.

Xulosa

  • PathPrefix(...).Subrouter() umumiy prefiksli marshrutlarni guruhlaydi. Ichki marshrutlar prefiksga nisbatan yoziladi.
  • Subrouter’lar ichma-ich joylashishi mumkin.
  • Har bir resursning Register funksiyasi o‘z marshrutlarini qo‘shadi, prefiks esa main.goda belgilanadi.
  • API versiyalari alohida subrouter’larda yonma-yon ishlaydi.
  • Middleware subrouter’ga qo‘shilsa, faqat shu guruhga ta’sir qiladi.
  • Subrouter host, sarlavha va boshqa shartlar bo‘yicha ham yaratiladi.

Keyingi darsda gorilla/muxda middleware’lar bilan ishlashning xususiyatlarini ko‘rib chiqamiz.