A detailed backend reference

Routing, di mana setiap request.

Method HTTP menjawab apa dari sebuah request, maksud Anda. Routing menjawab di mana: resource mana di server yang jadi sasaran maksud Anda. Manual ini membahas routing dari ujung ke ujung, route statis dan dinamis, parameter path dan query, nesting, versioning, dan catch-all, dengan router yang sudah jadi di Go dan Python.

the where of a request method + path -> handler 13 sections Versi Sederhana
Part I, The ModelThe core idea
01

Apa Itu Routing

Setiap request membawa dua hal sekaligus. Method HTTP menjawab "apa yang ingin kulakukan?", ambil, tambah, perbarui, atau hapus data. Itu maksud Anda, aksinya. Route menjawab "di mana yang ingin kulakukan?", resource mana, di alamat mana di server, tempat aksi itu mau dilakukan.

Anda harus memberi tahu server ke mana harus pergi. Kirim request GET (maksud: ambil) ke route /users (alamatnya), dan server membalas dengan larik users. Routing adalah mesin yang mengambil apa dan di mana, memetakannya ke handler tertentu, lalu membiarkan handler itu menjalankan logika bisnis dan mengembalikan data.

Routing, definition

Routing adalah memetakan path URL (plus method HTTP) ke logika sisi server: sebuah handler yang menjalankan logika bisnis, operasi database, dan apa pun yang diperlukan, lalu mengembalikan respons. Itu saja.

Istilah yang dipakai di manual ini

  • Maksud / aksi: apa yang ingin dilakukan request, diekspresikan oleh method HTTP (ambil, tambah, perbarui, hapus).
  • Resource: "benda" yang dikenai aksi request, biasanya kata benda di path seperti books atau users. (Resource dipelajari sungguh-sungguh di REST API; di sini kita perlakukan sekadar sebagai sasaran route.)
  • Route / route path / URL path: bagian alamat yang menyebut di mana di server Anda pergi, mis. /api/books.
  • Handler: rangkaian instruksi yang dijalankan server saat request cocok dengan route; ia pemilik logika untuk endpoint itu.
02

Method + Path -> Handler

Ini jantung routing. Server mengambil method dan route, lalu memperlakukan keduanya sebagai kunci unik yang memetakan ke satu handler. Method diperiksa dulu, lalu route, keduanya digabung, dan terbentuklah satu jalur routing unik.

Ambil dua request ke alamat yang sama. GET /api/books mengambil buku; POST /api/books membuat satu. Route-nya identik, tapi method-nya berbeda, jadi kuncinya berbeda dan keduanya menuju handler yang berbeda. Method membedakan dua route yang sebenarnya identik, dan kunci-kuncinya tidak pernah bentrok.

GET /api/books intent: fetch the books POST /api/books intent: add a book DELETE /api/books/9 intent: remove book 9 ROUTER method + path = unique key GET /books POST /books DEL /books/:id listBooks() -> returns all books createBook() -> creates, returns books deleteBook(id) -> removes the book
One key, one handler. The server checks method, then route, concatenates them into a unique key, and dispatches to the matching handler. Same path + different method = different destination.
The method is the "what" (brief)

Routing only needs to know the method as the other half of the key. The common verbs and their intent: GET fetch / POST add/create / PUT / PATCH update / DELETE remove. The deep study of methods belongs to the HTTP topic; here they simply pair with a route to form the key.

Part II, Route TypesShapes a route can take
03

Route Statis

Route statis adalah path tetap tanpa parameter variabel di dalamnya. /api/books statis: string-nya konsisten, ia sebuah konstanta. Anda selalu memakai string yang sama saat membuat request, tidak ada yang berubah di dalamnya, dan ia selalu mengembalikan jenis respons yang sama. Itulah persis kenapa disebut "statis".

Static route

String path konstan tanpa segmen dinamis, mis. /api/books. Dua contoh pertama dari sumber, GET dan POST di /api/books, keduanya route statis.

Mencocokkan route statis adalah perbandingan string literal. Kebanyakan API mulai di sini: satu endpoint yang mendaftar koleksi dan satu yang membuat item, keduanya di path konstan yang sama.

04

Route Dinamis & Parameter Path

Route dinamis punya parameter variabel di dalam path, jadi satu pola bisa melayani banyak resource spesifik. GET /api/users/123 mengambil detail satu pengguna, angka 123 adalah ID pengguna, yang diekstrak server dari route untuk mengerjakan tugasnya (ambil pengguna itu dari database lalu kembalikan).

Di sisi server, definisi route untuk ini tampak seperti method plus pola. Jika ditulis, bagian pencocokannya kira-kira seperti:

route definitionpattern
r.get("/api/users/:id", handler)
        //          +--- ":id" = a dynamic parameter slot

Bagian method mencocokkan GET; bagian path mencocokkan /api/users/ diikuti string apa pun. Konvensi titik dua, :id, berbunyi "segmen ini dinamis; klien memasok nilai di sini". Konvensi :name ini Anda temukan di Node, Python, Go, Rust, dan Java; ini praktik seluruh industri.

/api/users/ :id pattern (the route definition) /api/users/123 /api/users/abc /api/users/9f2 each binds -> id = "123" | "abc" | "9f2"
One pattern, many matches. The dynamic segment :id matches any string in that position and binds it to a named value the handler reads.
Path parameter (route parameter)

Nilai dinamis yang hidup di dalam path, tepat setelah garis miring. Karena ia bagian dari route, ia disebut path parameter atau route parameter, dan ia membawa makna semantik: ia mengidentifikasi resource mana yang dimaksud request.

  • Semua yang ada di path tiba sebagai string. 123 tampak seperti angka, tapi di route path angka dan karakter khusus semuanya diperlakukan sebagai string; handler meng-cast-nya kalau perlu.
  • Dibaca lantang, GET /api/users/123 berbunyi "ambil data pengguna yang ID-nya 123". Konstruk yang bisa dibaca manusia ini adalah seluruh gagasan di balik routing gaya REST, memberi route makna semantik yang jelas.
05

Parameter Query

Parameter query adalah pasangan key/value yang menempel di ujung route setelah tanda tanya. Beberapa pasangan disambung dengan ampersand. Contohnya, /api/search?query=some+value, route-nya /api/search, dan setelah ? datang key query dengan nilai some+value.

/api/search ? query = some+value & page = 2 & limit = 20 the route ? begins the query keys values & joins pairs
Structure. First pair after ?, the rest after &, each a key=value. A space in a value is written as +.

Kenapa parameter query ada

Request POST dan PUT punya body: wadah untuk mengirim data yang ditentukan pengguna ke server. Tapi request GET, menurut konvensi REST, tidak punya body. Lalu bagaimana GET mengirim nilai tambahan? Parameter query jawabannya: mereka membuat GET mengangkut seperangkat pasangan key/value yang membawa metadata tentang request: bukan identitas resource, melainkan instruksi untuk membentuk respons.

Query parameter

Pasangan key=value opsional di query string, dipakai untuk filter, sort, paginate, atau cari. Biasanya dipakai dengan request GET karena mereka tidak punya body. Opsional, bisa diulang, dan tidak tergantung urutan.

Contoh utamanya: pagination

Bayangkan endpoint yang mengembalikan daftar buku berformat halaman. Panggilan pertama, /api/books, memakai bawaan server dan mengembalikan sepotong data plus metadata yang menggambarkan keseluruhan set:

JSON / responseGET /api/books
{
  "data": [ /* ...the first 20 books (the default page)... */ ],
  "total":        100,   // how many books exist in total
  "currentPage":  1,     // which page this is (default = 1)
  "limit":        20,    // page size (default limit)
  "totalPages":   5      // 100 / 20 = 5 pages
}

Klien memakai metadata respons ini untuk membuat request berikutnya. Bawaan-nya tidak butuh parameter; untuk ke halaman dua klien mengirim /api/books?page=2. Mekanisme yang sama membawa filter (mis. filter berdasar nilai yang ditentukan pengguna) dan urutan sort (naik atau turun), semuanya sebagai pasangan key/value di query.

06

Parameter Path vs Query

Keduanya menaruh data di URL, dan gampang tertukar, jadi pantas dipisahkan dengan presisi. Keduanya melayani tujuan berbeda.

/api/users/123 ?sort=name&page=2 PATH PARAMETER Identifies WHICH resource. Part of the route. Semantic. e.g. user whose ID is 123. QUERY PARAMETER Shapes HOW it's returned. After the route. Metadata. filter / sort / paginate / search.
Rule of thumb. If it decides which thing you get -> path parameter. If it only changes the shape or subset of the response -> query parameter.

Parameter path melayani satu tujuan khusus: ekspresi semantik resource mana, "pengguna yang ID-nya 123". Secara teknis Anda bisa mendorong nilai pencarian ke path (/api/search/some+value), tapi sulit dirawat dan meniadakan tujuan memberi route makna semantik yang jelas. Metadata seperti istilah pencarian pantasnya di query string: /api/search?query=some+value.

07

Route Bertingkat (Nested)

Nesting sebenarnya bukan jenis route yang terpisah, melainkan praktik yang akan Anda lihat di mana-mana. Di API bergaya REST, Anda sering menyarangkan resource berbeda untuk mengekspresikan hubungan semantik. Tiap kedalaman menambah menyempitkan makna dan memetakan ke handler-nya sendiri, mengembalikan granularitas data yang berbeda.

/api/users -> list all users /api/users/123 -> one user /api/users/123/posts -> that user's posts /api/users/123/posts/456 -> one post
Each level is its own endpoint. Stop at any rung and you get a different response: all users, one user, that user's posts, or one specific post.

Menyusuri contoh terdalam, /api/users/123/posts/456:

  • /api/users, bagian statis; sendirian ia mengembalikan daftar semua pengguna.
  • 123, path parameter untuk ID pengguna; /api/users/123 mengembalikan pengguna itu satu.
  • posts, bagian statis yang menamai sub-koleksi; /api/users/123/posts mengembalikan semua post pengguna itu.
  • 456, path parameter untuk sebuah post spesifik; route lengkapnya mengambil "post dengan ID 456 milik pengguna dengan ID 123".

Itulah kenapa disebut route bertingkat: Anda menyarangkan berbagai jenis informasi di tingkat yang berbeda untuk mengekspresikan makna semantik yang berbeda, dan masing-masing menghasilkan respons yang berbeda. Praktik ini akan Anda lihat hampir di mana-mana begitu API punya kompleksitas sedang saja.

Part III, Lifecycle & EvolutionAt run-time & over time
08

Siklus Hidup Routing

Merangkai semuanya, inilah jalur yang ditempuh satu request, langkah routing dalam konteksnya.

1 / ARRIVE GET /api/users/123 2 / READ split route / query 3 / MATCH method+route -> route 4 / EXTRACT id = 123 5 / HANDLER business logic / DB 6 / RESPOND return the data
Six beats. Arrive -> read the route -> match (the routing step) -> extract parameters -> run the handler -> return the response.
09

Versioning & Deprecation

Route versioning adalah praktik yang sangat umum untuk mengelola perubahan di endpoint API. Anda menaruh kata kunci versi di route, /api/v1/products dan /api/v2/products. Ia tampak seperti route biasa dengan segmen v1 atau v2 ditambahkan.

Kenapa repot? Anggap v1 mengembalikan tiap produk sebagai { id, name, price }. Belakangan muncul kebutuhan baru, misalnya Anda kini melayani aplikasi mobile dan harus mengubah bentuk respons, menamai ulang name menjadi title. Jika route yang hidup diubah, semua klien yang ada rusak. Versioning membuat kedua bentuk hidup berdampingan:

/api/v1/products/api/v2/products
{ id, name, price }{ id, title, price }

Ini melakukan dua hal. Ia mengekspresikan maksud dengan jelas, v1 dan v2 dari respons berdampingan. Dan artinya Anda tidak perlu mengubah seluruh route (tidak perlu /api/new-products); cukup menaikkan versi.

time -> /api/v1/products, live /api/v2/products, live MIGRATION WINDOW v2 ships v1 deprecated -> removed
The safe workflow. v2 ships beside v1; clients get a window to migrate; then v1 is deprecated and removed. Eventually v2 may be promoted to the default.
Deprecation

Mengumumkan bahwa sebuah endpoint (mis. v1) akan pensiun begitu v2 rilis. Para engineer mendapat jendela migrasi yang jelas untuk memindahkan request mereka ke v2; hanya setelah itu tim menghapus v1. Versioning ditambah deprecation memberi alur kerja yang stabil untuk mengirim perubahan yang memecah (breaking changes) tanpa memecah klien pada saat itu juga.

10

Route Catch-All

Route catch-all menangani request untuk route yang tidak benar-benar dilayani server. Anggap datang request untuk /api/v3/products tapi tidak ada handler-nya. Server menaruh catch-all paling akhir, setelah semua route dan method spesifik, biasanya ditulis dengan wildcard, /*. Request apa pun yang lolos dari semua kecocokan sebelumnya mendarat di sana.

incoming: GET /api/v3/products (no handler exists) match GET /api/v1/products ? No match GET /api/v2/products ? No match GET /api/users/:id ? No match /* (catch-all) Yes friendly 404 "this route does not exist"
Fall-through. Because it sits last, the catch-all only fires when nothing else matched, turning a confusing default into a clear "not found".

Tanpa penanganan catch-all, perilaku bawaannya sering berupa respons null, yang membingungkan. Dengan itu, handler mengembalikan pesan ramah pengguna, "route ini tidak ada" / "route not found", jadi klien paham jelas bahwa server tidak melayani endpoint itu.

Wildcard & ordering

Wildcard * mencocokkan apa pun, persis itulah kenapa catch-all wajib didaftarkan paling akhir. Ditaruh di awal, ia bakal mencegat semua request sebelum route yang asli mendapat giliran. Aturan yang bisa diandalkan: route spesifik dulu, catch-all di paling ujung.

Part IV, ImplementationBuild a router, with the OOP showing
11

Sebuah Router di Go

Router, pada dasarnya, adalah masalah dispatch berorientasi objek kecil: sebuah tabel dari "method + route" ke handler. Go mengekspresikan empat pilar OOP lewat interface (abstraksi + polimorfisme), field tak-terekspor (enkapsulasi), dan struct embedding (komposisi, bentuk inheritance Go). Komentar ber-tag OOP menunjuk masing-masing.

Go 1.22+router.go
package main

import ("fmt"; "strings")

// ABSTRACTION: Handler is an interface, it names a capability
// ("be able to Handle a request") without saying how. Any type
// with a Handle method IS-A Handler. The router depends only on
// this contract, never on a concrete type.
type Handler interface {
    Handle(req Request) Response
}

type Request struct {
    Method string
    Path   string
    Params map[string]string // extracted path params (:id)
    Query  map[string]string // query params (?page=2)
}
type Response struct { Status int; Body string }

// ENCAPSULATION: "routes" is lowercase => unexported => private to
// the package. Outsiders cannot touch the table directly; they go
// through the methods we expose (Register / Dispatch).
type Router struct {
    routes map[string]Handler // key = "METHOD /route"
}

func NewRouter() *Router {
    return &Router{routes: make(map[string]Handler)}
}

// Register binds method + route -> handler (the dispatch table).
func (r *Router) Register(method, pattern string, h Handler) {
    r.routes[method+" "+pattern] = h // method + path = the unique key
}

// POLYMORPHISM: Dispatch calls h.Handle(req) without knowing which
// concrete type h is. Same call, different behaviour per handler.
func (r *Router) Dispatch(req Request) Response {
    for key, h := range r.routes {
        method, pattern, _ := strings.Cut(key, " ")
        if method == req.Method && match(pattern, req.Path, &req) {
            return h.Handle(req) // polymorphic dispatch
        }
    }
    return Response{404, "route not found"} // catch-all fallback
}

// match compares "/users/:id" against "/users/123", filling Params.
func match(pattern, path string, req *Request) bool {
    p, q := strings.Split(pattern, "/"), strings.Split(path, "/")
    if len(p) != len(q) { return false }
    for i := range p {
        if strings.HasPrefix(p[i], ":") { // dynamic segment
            req.Params[p[i][1:]] = q[i] // bind :id => "123"
        } else if p[i] != q[i] { // static segment must match exactly
            return false
        }
    }
    return true
}

// "INHERITANCE" via COMPOSITION: BaseHandler holds shared logic;
// concrete handlers EMBED it and reuse its methods.
type BaseHandler struct{ Name string }

func (b BaseHandler) log(req Request) {
    fmt.Printf("[%s] %s %s\n", b.Name, req.Method, req.Path)
}

// GetUser embeds BaseHandler (composition) and satisfies Handler.
type GetUser struct{ BaseHandler }
func (h GetUser) Handle(req Request) Response {
    h.log(req) // reused (inherited) behaviour
    return Response{200, "user id = " + req.Params["id"]}
}

type ListBooks struct{ BaseHandler }
func (h ListBooks) Handle(req Request) Response {
    h.log(req)
    page := req.Query["page"] // query param drives pagination
    if page == "" { page = "1" }
    return Response{200, "books page " + page}
}

func main() {
    r := NewRouter()
    r.Register("GET", "/api/users/:id", GetUser{BaseHandler{"users"}})
    r.Register("GET", "/api/books",     ListBooks{BaseHandler{"books"}})

    req := Request{Method: "GET", Path: "/api/users/123",
        Params: map[string]string{}, Query: map[string]string{}}
    fmt.Println(r.Dispatch(req).Body) // => user id = 123
}
12

Sebuah Router di Python

Arsitektur yang sama dalam sintaks kelas klasik: abstract base class untuk kontraknya (abstraksi), subclassing sungguhan (inheritance), method yang di-override (polimorfisme), dan atribut privat yang di-name-mangle (enkapsulasi).

Python 3.11+router.py
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from urllib.parse import urlsplit, parse_qs

# ABSTRACTION: Handler is an Abstract Base Class. @abstractmethod
# forces every subclass to define handle(). You cannot create a
# Handler directly, it is a pure contract.
class Handler(ABC):
    @abstractmethod
    def handle(self, req: "Request") -> "Response": ...

@dataclass
class Request:
    method: str
    path: str
    params: dict = field(default_factory=dict)  # path params (:id)
    query: dict = field(default_factory=dict)   # query params (?page=2)

@dataclass
class Response:
    status: int
    body: str

# INHERITANCE: BaseHandler is a concrete parent holding shared
# behaviour (logging). Subclasses inherit and reuse _log().
class BaseHandler(Handler):
    def __init__(self, name): self._name = name   # protected by convention
    def _log(self, req): print(f"[{self._name}] {req.method} {req.path}")

# POLYMORPHISM: each subclass OVERRIDES handle() differently, yet
# the router calls them all identically, handler.handle(req).
class GetUser(BaseHandler):           # IS-A BaseHandler IS-A Handler
    def handle(self, req):
        self._log(req)              # inherited from parent
        return Response(200, f"user id = {req.params['id']}")

class ListBooks(BaseHandler):
    def handle(self, req):
        self._log(req)
        page = req.query.get("page", ["1"])[0]   # query param
        return Response(200, f"books page {page}")

# ENCAPSULATION: self.__routes is name-mangled to _Router__routes,
# so it is effectively private. The public surface is register()
# and dispatch(); the table stays hidden.
class Router:
    def __init__(self):
        self.__routes: dict[tuple[str, str], Handler] = {}

    def register(self, method, pattern, handler):
        self.__routes[(method, pattern)] = handler  # method + path key

    def dispatch(self, method, url):
        parts = urlsplit(url)
        query = parse_qs(parts.query)               # ?a=1&b=2 -> dict
        for (m, pattern), handler in self.__routes.items():
            params = self.__match(pattern, parts.path)
            if m == method and params is not None:
                return handler.handle(
                    Request(method, parts.path, params, query))  # polymorphic
        return Response(404, "route not found")       # catch-all

    @staticmethod
    def __match(pattern, path):
        p = pattern.strip("/").split("/")
        q = path.strip("/").split("/")
        if len(p) != len(q): return None
        params = {}
        for seg, val in zip(p, q):
            if seg.startswith(":"):       # dynamic segment
                params[seg[1:]] = val    # bind :id -> "123"
            elif seg != val:            # static must match exactly
                return None
        return params

if __name__ == "__main__":
    r = Router()
    r.register("GET", "/api/users/:id", GetUser("users"))
    r.register("GET", "/api/books",     ListBooks("books"))
    print(r.dispatch("GET", "/api/users/123").body)   # user id = 123
    print(r.dispatch("GET", "/api/books?page=2").body) # books page 2
Same architecture, two languages

Go meraihnya lewat interface + embedding; Python lewat ABC + pewarisan kelas. Keduanya adalah tabel dispatch privat ber-kunci method + route, kontrak handler abstrak, dispatch polimorfik, dan fallback catch-all, seluruh model routing dari manual ini, dalam kode.

13

Glosarium Routing

Setiap istilah routing yang dipakai di atas, dalam satu tempat.

IstilahMakna
RoutingMemetakan path URL (+ method HTTP) ke handler / logika sisi server.
Maksud / aksiApa yang ingin dilakukan request, ambil, tambah, perbarui, hapus (method HTTP).
Route / route pathBagian alamat yang menyebut di mana di server, mis. /api/books.
HandlerInstruksi yang berjalan saat route cocok; menjalankan logika bisnis.
Resource"Benda" yang disasar route, kata benda seperti users atau books.
Kunci unikMethod + route yang digabung; itulah kunci server mencari handler.
Route statisPath tetap dan konstan tanpa parameter variabel.
Route dinamisRoute dengan parameter variabel di dalam path.
Parameter pathNilai dinamis di dalam path (:id); mengidentifikasi resource mana. Disebut juga "route parameter".
Parameter queryPasangan key=value setelah ?; metadata untuk filter/sort/paginate/cari.
PaginationMengembalikan daftar dalam halaman, digerakkan parameter query seperti page dan limit.
Route bertingkatHirarki segmen yang mengekspresikan kepemilikan, /users/123/posts/456.
Makna semantikRoute yang ditulis supaya terbaca seperti pernyataan jelas ramah manusia (tujuan routing REST).
VersioningMenaruh /v1/, /v2/ di route untuk mengembangkan API dengan aman.
DeprecationMenandai versi lama hendak pensiun; klien mendapat jendela migrasi.
Route catch-allRoute /* pilihan terakhir yang mengembalikan "route not found" yang ramah.
WildcardToken (*) yang mencocokkan apa pun; hidup di catch-all.
The whole model in one breath

Sebuah request menggabung method (apa) dan route (di mana). Server menyatukannya menjadi kunci unik, mencocokkannya ke tabelnya, route statis dulu, dinamis berikutnya, catch-all terakhir, mengekstrak path param dari route dan query param dari query string, lalu memanggil handler yang cocok. Beri versi pada route untuk berkembang, deprecate untuk pensiun, dan catch-all untuk gagal dengan sopan.