Identyfikacja versus kształt: wybór parametrów trasy czy ciągów zapytań w Express
Dowiedz się, kiedy wartość powinna znajdować się w parametrze trasy Express, a kiedy w łańcuchu zapytania, jak odczytywać req.params i req.query oraz jak bezpiecznie obsługiwać wartości domyślne i typy danych.
Weźmy URL /users/42?sort=name&order=asc. Liczba 42 wskazuje konkretnego użytkownika; sort=name&order=asc jedynie zmienia sposób uporządkowania odpowiedzi. Mieszanie tych dwóch funkcji, na przykład traktowanie ID jako filtru lub filtru jako ID, jest typową przyczyną problematycznych tras w Express. Po przeczytaniu tego przewodnika będziesz miał prosty test, aby określić, do której kategorii należy dana wartość, oraz będziesz wiedział, jak Express eksponuje każdy rodzaj takich parametrów i jakie niespodzianki mogą się pojawić.
Parametry trasy identyfikują zasób
Parametr trasy to nazwany element, który stanowi część samego wzoru ścieżki. Informuje on serwer o tym, jaki konkretny zasób jest przedmiotem żądania.
/users/:id
Kolon oznacza :id jako miejsce zastępcze. Gdy przychodzi żądanie do /users/42, Express dopasowuje ten wzorzec i zapisuje 42 jako wartość pola id. Ta sama zasada obowiązuje dla każdego zasobu, który ma identyfikator:
/users/42 → which user
/products/17 → which product
/orders/1042 → which order
Każda z tych ścieżek oznacza dokładnie jedną rzecz. Bez tego segmentu pytanie „która?” nie ma odpowiedzi.
Łańcuchy zapytań kształtują odpowiedź
Łańcuch zapytania to wszystko, co znajduje się po znaku ?: lista par klucz=wartość połączonych znakiem &. Nie służy do wyboru zasobu, lecz do filtrowania, sortowania, paginacji lub innej modyfikacji tego, co zostanie zwrócone.
/users?sort=name&order=asc
Tutaj metody sort i order nie zmieniają samego zasobu (kolekcji użytkowników), a jedynie wpływają na jego prezentację. Przykłady bardziej typowe:
/products?category=electronics&maxPrice=500
/search?q=laptop&page=2
Jeden test, by odróżnić te przypadki
Zadaj pytanie, co się stanie po usunięciu tej wartości:
- Jeśli ścieżka przestanie mieć sens (nie można pobrać „użytkownika” bez określenia, który to), wartość ta jest identyfikatorem i powinna znajdować się w ścieżce.
- Jeśli ścieżka nadal działa i po prostu zwraca domyślny, niefiltrowany wynik (wszyscy użytkownicy w standardowym porządku), wartość ta jest modyfikatorem i powinna znajdować się w ciągu zapytania.
Test ten wskazuje również na konieczność obsługi błędów. Brak zasobu za parametrem ścieżki zazwyczaj powinien skutkować kodem 404, natomiast filtr, który nic nie dopasowuje, powinien zwykle zwracać kod 200 wraz z pustym списком. Aby dowiedzieć się więcej na temat projektowania URL skoncentrowanych na zasobach, zapoznaj się z REST API dla początkujących.
Czytanie parametrów ścieżki za pomocą req.params
Parametry są deklarowane za pomocą kropki kolonowej w ścieżce trasy, a ich wartości pojawiają się w req.params pod tymi samymi nazwami:
app.get("/users/:id", (req, res) => {
const userId = req.params.id;
res.send(`Fetching user with ID: ${userId}`);
});
Zapytanie do /users/42 ustawia req.params.id na ciąg znaków "42".
Kilka parametrów w jednej ścieżce
Zasoby ułożone w hierarchii po prostu deklarują dodatkowe miejsca zastępcze:
app.get("/users/:userId/orders/:orderId", (req, res) => {
const { userId, orderId } = req.params;
res.send(`User ${userId}, Order ${orderId}`);
});
Dla /users/42/orders/1042 destrukcja zwraca userId równe "42" oraz orderId równe "1042". Nazwy parametrów muszą być unikalne w ramach danej trasy i powinny opisywać to, co identyfikują; userId i orderId są znacznie lepiej czytelne niż dwa anonimowe id.
Czytanie ciągów zapytań za pomocą req.query
Wartości zapytania nie wymagają deklaracji w trasie. Express analizuje wszystko, co znajduje się po ?, i umieszcza to w req.query:
app.get("/users", (req, res) => {
const { sort, order } = req.query;
res.send(`Sorting by ${sort}, order: ${order}`);
});
Dla /users?sort=name&order=asc otrzymujemy req.query.sort w postaci "name", a req.query.order w postaci "asc". Sama trasa pozostaje /users, więc ten sam obsługiwacz obsługuje zarówno zwykłe, jak i posortowane żądania.
Dostarczanie domyślnych wartości dla opcjonalnych parametrów
Ponieważ klienci często pomijają wartości zapytania, obsługiwacze zazwyczaj korzystają z rozsądnych domyślnych wartości:
app.get("/products", (req, res) => {
const sort = req.query.sort || "default";
const page = req.query.page || 1;
res.send(`Sorting: ${sort}, Page: ${page}`);
});
Zwykły żądanie /products nadal jest skuteczne, przy użyciu wartości domyślnych. Należy zwrócić uwagę na jedną subtelność: gdy klient faktycznie wysyła page=2, page to ciąg znaków "2", natomiast gdy jest pominęty, to liczba 1. Takie mieszane typy powodują późniejsze błędy (np. łączenie ciągów znaków zamiast ich dodawania). Konwertuj je wyraźnie, na przykład za pomocą Number(req.query.page) || 1, i zweryfikuj wynik przed użyciem go w zapytaniu do bazy danych.
Wartości nie zawsze są pojedynczymi ciągami znaków
Klucz powtarzający się w adreście URL, taki jak ?tag=a&tag=b, trafia jako tablica, a nie ciąg znaków. W zależności od ustawień parsera zapytań, składnia nawiasów może również tworzyć zagnieżdżone obiekty. Express 5 zmienił domyślnego parsera na prostszy niż ten używany w Express 4, więc sprawdź dokumentację swojej wersji, jeśli polegasz na zagnieżdżonych obiektach zapytań. W każdym przypadku nigdy nie zakładaj typu wartości zapytania; traktuj req.query jako niepewny wprowadzony danych.
Decydowanie, który typ jest potrzebny dla danej ścieżki
Parametry ścieżki dla konkretnego zasobu
app.get("/users/:id", ...) // one specific user
app.get("/products/:id", ...) // one specific product
app.get("/orders/:orderId", ...) // one specific order
Każda z tych ścieżek odnosi się do jednego, konkretnego elementu. Jeśli ścieżka jest bezsensowna bez tej wartości, umieść ją w ścieżce.
Ciągi zapytań do filtrowania, sortowania i paginacji
app.get("/users", ...) // ?role=admin&status=active
app.get("/products", ...) // ?category=electronics&maxPrice=500&sort=price
app.get("/search", ...) // ?q=laptop&page=2
Każdy z tych przypadków ma sens nawet bez żadnej wyszukiwarki: „wszyscy użytkownicy”, „wszystkie produkty” lub pusta strona wyszukiwania. Wartości, które jedynie zawężają lub przestawiają wyniki, są opcjonalnymi modyfikatorami i powinny znajdować się po znaku ?.
Łączenie obu w jednej trasie
Rzeczywiste punkty końcowe często używają ich jednocześnie. Parametr określa właściciela, a wyszukiwarka zawęża powiązane dane:
app.get("/users/:id/orders", (req, res) => {
const userId = req.params.id; // which user
const status = req.query.status; // optional filter: only their pending orders, for example
res.send(`Orders for user ${userId}, filtered by status: ${status || "all"}`);
});
Prośba o adres /users/42/orders?status=pending brzmi naturalnie: 42 wskazuje, czyje zamówienia, a status=pending określa, które z tych zamówień należy uwzględnić. Gdy parametr status jest brakujący, obsługa zwraca informację „wszystkie”, co odpowiada koncepcji, że brak modyfikatora oznacza wyniki niefiltrowane.
Częste pytania
Czy jedna trasa może używać obu?
Tak, i jest to bardzo powszechne. Przykład pokazany powyżej stanowi typowy wzorzec: identyfikator zasobu nadrzędnego w połączeniu z opcjonalnymi filtrami dla jego potomków.
Czy parametry są zawsze wymagane, a wartości zapytania zawsze opcjonalne?
To silna konwencja, a nie sztywna zasada. Express faktycznie obsługuje opcjonalne segmenty ścieżki, a nic nie przeszkadza API w wymaganiu wartości zapytania. Mimo to obowiązuje praktyczna zasada: identyfikatory wymagane umieszcza się w ścieżce, a opcjonalne modyfikatory – w zapytaniu z domyślnymi wartościami.
Czy req.params.id to liczba?
Nie. Wszystko, co jest wydobywane z URL, to ciąg znaków, nawet jeśli wygląda jak liczba. Konwertuj to wyraźnie, na przykład za pomocą Number(req.params.id), i odrzuć wartości, które dają wynik NaN, zanim dotrzesz do bazy danych.
Co robić, jeśli brakuje oczekiwanej wartości zapytania?
Klucz po prostu nie występuje w req.query, więc jego odczytanie zwraca wartość undefined. Właśnie dlatego wcześniej przedstawione domyślne rozwiązania awaryjne są standardową praktyką.
Podsumowanie
Oba rodzaje wartości znajdują się w tej samej adrese URL, ale pełnią różne funkcje. Parametry trasy, odczytywane z req.params, określają konkretny temat żądania. Ciągi zapytań, odczytywane z req.query, wpływają na sposób filtrowania, sortowania lub paginacji odpowiedzi i powinny mieć domyślne wartości. Traktuj je oba jako ciągi znaków bez typu pochodzące ze świata zewnętrznego: konwertuj je i waliduj przed użyciem, a projekt Twoich tras będzie przewidywalny w miarę rozwoju API.
Literatura pokrewna
- Ochrona granic Express: Jedno Zod Middleware dla ciała żądania, parametrów i ciągów zapytania — Dowiedz się, jak weryfikować treść żądań Express, parametry trasy oraz ciągi zapytania za pomocą jednego wielokrotnie używalnego middleware Zod oraz jak uzupełnia on weryfikację modeli Sequelize.
- REST API dla początkujących: Zasoby, metody, kody stanu i brak stanu — Przewodnik w prostym języku wyjaśniający, czym jest REST API, pięć zasad, które sprawiają, że funkcjonuje, gdzie jest wykorzystywany w rzeczywistych zespołach oraz jak stworzyć i przetestować swój pierwszy REST API.