1. GET як домовленість про читання
Коли шлях перестає бути командою і стає іменем ресурсу, перше природне запитання — як читати цей ресурс.
Коли ви вперше бачите HTTP, GET часто здається чимось на кшталт «ну, це коли я просто відкриваю посилання». І так, браузер справді найчастіше надсилає GET. Але в бекенд-мисленні GET — це не «метод за замовчуванням», а домовленість про читання: клієнт просить сервер показати стан ресурсу і очікує, що сам запит нічого не змінить, не створить, не оновить і не видалить.
Уявіть бібліотеку. GET — це коли ви підійшли до бібліотекаря і сказали: «Покажіть, будь ласка, що у вас є на тему “чистий код”». Ви не просите бібліотекаря переписати книгу, не просите додати нову книгу до фонду — ви просто хочете інформацію. В HTTP логіка та сама: GET — про отримати дані, а не про змінити дані.
Важливо ще ось що: на практиці GET буде одним із найчастіших запитів у вашому коді. Навіть коли ви робите CRUD, операцій читання зазвичай більше, ніж змін. Тому якщо ви навчитеся правильно мислити GET уже зараз, вам потім буде легше і з перевіркою в Postman, і з написанням Java-клієнта, і з розумінням серверної частини.
Мінінагадування у вигляді «рядка запиту» — просто щоб тримати перед очима форму method + path:
// Рядок запиту (request line) у найпростішому вигляді: "метод + шлях"
String requestLine = "GET /reading-list";
// Друкуємо рівно те, що «йде» в запиті на цьому рівні абстракції
System.out.println(requestLine); // GET /reading-list
Тут немає жодної магії: це просто зрозуміла для людини форма «метод + шлях».
2. Ресурс: колекція та елемент
Для GET нам потрібне лише коротке нагадування: ресурс зазвичай живе у двох формах — як колекція та як окремий елемент. У ReadLater це все та сама знайома пара: /reading-list означає весь список, а /reading-list/42 — один конкретний елемент.
Один і той самий GET працює в обох випадках, але ціль різна: або читаємо набір цілком, або читаємо обʼєкт за id. Цієї різниці вже досить, щоб далі не вигадувати /getReadingList і /getItem: шлях сам показує, що саме ми читаємо.
// Шлях до колекції: «усі елементи»
String collectionPath = "/reading-list";
// Шлях до одного елемента: той самий шлях + ідентифікатор наприкінці
String itemPath = "/reading-list/42";
System.out.println(collectionPath); // /reading-list
System.out.println(itemPath); // /reading-list/42
3. GET колекції: «дай список»
Коли клієнт робить GET до шляху колекції, він каже просту річ: «Покажи мені набір елементів цього типу». Це може бути порожній набір, може бути великий набір, але зміст один: клієнт читає колекцію. У домені ReadLater це буде читання вашого списку читання цілком: що ви планували, що читаєте, що вже завершили.
Важливо не намагатися вбудувати дію в URL. Типова для новачків звичка — робити так: /getReadingList. Але GET уже означає «get». Виходить «get-get-reading-list», тобто подвійне «покажи-покажи». У програмуванні такі речі зазвичай закінчуються тим, що десь з’являється getGetGet(), і далі вже пізно.
У термінах запитів це виглядає так:
// Читання всієї колекції без умов
String listAll = "GET /reading-list";
// Читання колекції з фільтром через query-параметри
String listPlanned = "GET /reading-list?status=PLANNED";
System.out.println(listAll); // GET /reading-list
System.out.println(listPlanned); // GET /reading-list?status=PLANNED
Ми поки не обговорюємо, як саме сервер відповідає — які статуси, які заголовки, який JSON. Нам зараз важливий сенс: обидва рядки — це читання колекції, просто у другому варіанті клієнт просить не все підряд, а лише за умовою.
Щоб відчути це «на пальцях» без сервера і без мережі, можна уявити, що колекція вже лежить у пам’яті як список, а читання колекції — це просто «повернути весь список»:
import java.util.List;
// Колекція «ніби вже існує», і ми просто дивимося на неї
List<String> titles = List.of("Clean Code", "Effective Java", "Refactoring");
// size() — це «скільки елементів у колекції»
System.out.println(titles.size()); // 3
// Друк списку — це «покажи всі елементи»
System.out.println(titles); // [Clean Code, Effective Java, Refactoring]
Це не реалізація API — ще зарано, — а просто аналогія: запит до колекції означає «дай список».
4. GET елемента: за ідентифікатором
Тепер друга форма ресурсу: окремий елемент колекції. Коли клієнт робить GET /reading-list/42, він каже: «Покажи мені елемент списку читання з ідентифікатором 42». Тут ключова думка така: ідентифікатор обирає обʼєкт, а не дію. Це не команда «прочитай 42», це адреса конкретного ресурсу.
Ідентифікатор може бути числом, як у нашому локальному списку читання, а може бути рядком, як у зовнішньому каталозі книг, де трапляються зовнішні ID на кшталт OL12345M. Але принцип один: наприкінці шляху стоїть ключ, який відрізняє один елемент від іншого.
Знову на рівні «рядка запиту»:
// Два запити до двох різних елементів однієї й тієї самої колекції
String getItem42 = "GET /reading-list/42";
String getItem43 = "GET /reading-list/43";
System.out.println(getItem42); // GET /reading-list/42
System.out.println(getItem43); // GET /reading-list/43
Якщо продовжити аналогію з in-memory-даними, читання одного ресурсу — це як доступ до Map за ключем:
import java.util.Map;
// «Ідентифікатор -> значення» як проста модель ресурсу в памʼяті
Map<Long, String> readingList = Map.of(
42L, "Clean Code",
43L, "Effective Java"
);
// get(42L) — це «дай елемент за ID», дуже схоже за змістом на /reading-list/42
System.out.println(readingList.get(42L)); // Clean Code
Ми не будуємо репозиторій і не пишемо CRUD — ми просто закріплюємо сенс. GET до одного ресурсу — це «дай значення за ключем».
5. GET з query params: фільтр колекції
З query-параметрами в GET найчастіше й починається плутанина. Здається, що якщо ми додали ?щось=щось, то вже йдеться не про колекцію, а про щось інше. Насправді в нашій прикладній моделі простіше думати так: query-параметри уточнюють запит до колекції, але не перетворюють його на запит одного конкретного елемента.
Порівняйте два речення — і читайте їх як людина, а не як компілятор:
GET /reading-list/42 — «дай елемент з ID 42».
GET /reading-list?title=clean — «дай елементи зі списку читання, у назві яких є “clean”».
У другому випадку ми наперед не знаємо, скільки елементів підійде за умовою: нуль, один, два, десять. Тому це залишається читанням колекції, просто з фільтром.
У вигляді простого конструктора URL — на рівні рядків — це можна побачити так:
// Базовий шлях колекції
String basePath = "/reading-list";
// «Хвіст» query-параметрів (у реальному світі він часто збирається з різних частин)
String query = "status=PLANNED&title=clean";
// Склеювання дає той самий вигляд URL, який буде в запиті
String filtered = basePath + "?" + query;
System.out.println(filtered); // /reading-list?status=PLANNED&title=clean
А щоб закріпити сенс фільтрації на звичному Java-коді, знову без сервера, уявіть, що ми фільтруємо колекцію:
import java.util.List;
// Вихідні дані «ніби вже отримали»
List<String> titles = List.of("Clean Code", "Clean Architecture", "Effective Java");
// Фільтрація за підрядком: це не вибір за ID, а вибір підмножини
var filtered = titles.stream()
// Приводимо до нижнього регістру, щоб пошук був простішим
.filter(t -> t.toLowerCase().contains("clean"))
.toList();
System.out.println(filtered); // [Clean Code, Clean Architecture]
Фільтрація не обирає один конкретний елемент за ID — вона обирає підмножину колекції.
6. Карта GET-сценаріїв ReadLater
Тепер зберімо таблицю запитів, щоб мозок звик: «Ага, ось це читання списку, а ось це читання конкретного елемента». Вона потрібна не для того, щоб ви вивчили рядки напам’ять, а для того, щоб такі форми впізнавалися з першого погляду. Тоді під час читання контракту ви одразу розумієте, просить клієнт список чи конкретний обʼєкт.
Нижче — табличка з прикладами. Ми спеціально тримаємо її простою і не обговорюємо тут статуси та заголовки.
| Що хоче клієнт | Який ресурс читаємо | Приклад запиту | Людський сенс |
|---|---|---|---|
| Переглянути весь список читання | Колекція | GET /reading-list | «Покажи все, що є у моєму списку» |
| Переглянути один елемент списку | Один ресурс | GET /reading-list/42 | «Покажи елемент № 42» |
| Показати тільки заплановане | Колекція (з фільтром) | GET /reading-list?status=PLANNED | «Покажи тільки заплановане» |
| Знайти за частиною назви | Колекція (з фільтром) | GET /reading-list?title=clean | «Покажи, де в назві є clean» |
І додамо два приклади для зовнішнього каталогу книг. У нас далі буде етап Catalog Client, але вже зараз форми зрозумілі. Тут ми не фіксуємо реальний шлях провайдера, а просто показуємо загальну ідею:
// Пошук: читання колекції з умовою (query-параметр)
String catalogSearch = "GET /catalog/books?q=clean code";
// Деталі: читання одного ресурсу за ідентифікатором
String catalogDetails = "GET /catalog/books/OL12345M";
System.out.println(catalogSearch); // GET /catalog/books?q=clean code
System.out.println(catalogDetails); // GET /catalog/books/OL12345M
Сенс такий самий: пошук — це читання колекції з умовою, а деталі — це читання одного ресурсу за ідентифікатором.
7. Типові помилки з GET
Помилки з GET зазвичай виглядають нешкідливо, тому що «ну, запит же відправився, що може піти не так». Проблема в тому, що неправильні форми GET майже завжди роблять API менш передбачуваним, а отже — менш зручним і для клієнта, і для вас через пару тижнів, коли ви забудете, як тут було задумано.
Помилка №1: додавати дієслово в шлях і дублювати зміст методу.
Коли ви пишете /getReadingList, ви фактично повідомляєте двічі одне й те саме: метод GET уже говорить «прочитай», і шлях теж намагається сказати «прочитай». Набагато стійкіша звичка — «шлях = імʼя ресурсу», а «дія = метод». У підсумку це економить нерви, бо вам не потрібно вигадувати окремі шляхи на кожну дію.
Помилка №2: плутати вибір одного елемента і фільтрацію колекції.
GET /reading-list/42 і GET /reading-list?title=clean — це два різні сценарії. Перший передбачає, що клієнт знає ідентифікатор і хоче конкретний обʼєкт. Другий передбачає, що клієнт хоче підібрати кілька придатних обʼєктів. Якщо змішати ці сенси, ви дуже швидко прийдете до дивних адрес на кшталт /reading-list/clean, де незрозуміло: clean — це ID чи фільтр?
Помилка №3: використовувати GET як «універсальну кнопку запуску дії».
Іноді хочеться зробити «зручно»: /reading-list/remove/42 або /reading-list/42/delete. Але ви тим самим ламаєте ресурсну модель і перетворюєте API на набір команд. Сьогодні ми спеціально тримаємося правила: GET — це читання. Про створення, заміну, часткову зміну та видалення ми поговоримо в окремих лекціях.
Помилка №4: вважати, що query-параметри перетворюють запит на «інший ресурс», і починати плодити ендпоінти.
Часто новачки роблять так: якщо потрібно «показати заплановане», вони створюють /reading-list-planned, а якщо потрібно «показати finished» — /reading-list-finished. Це працює, але дуже швидко перетворюється на зоопарк адрес. Набагато простіше зберегти один шлях колекції й уточнювати вибір через query-параметри в розумних межах.
Помилка №5: намагатися заздалегідь «ідеально спроєктувати всі варіанти читання» і ускладнити API раніше часу.
На старті корисніше триматися базових форм: колекція, елемент, прості фільтри. Якщо ви почнете будувати суперрозумний DSL для пошуку одразу, ви витратите сили не на розуміння HTTP, а на винахід мови запитів.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ