Пять эндпоинтов сервиса заметок#
Продолжим сервис заметок из прошлого урока. Для CRUD нужно пять эндпоинтов: список, одна заметка, создание, обновление, удаление. В стандартной библиотеке со времён Go 1.22 это mux.HandleFunc("GET /notes/{id}", ...) — работает, но всё остальное (группы, middleware на пачку маршрутов, аккуратные 405) вы собираете сами. В Fiber каждый эндпоинт — один вызов с понятным именем метода, а параметры пути достаются из контекста одной строкой.
Справедливости ради: если вам нужен только маршрутизатор без остального, chi или встроенный mux закроют вопрос. Ценность Fiber начинается, когда к маршрутам добавляются middleware, валидация и унифицированные ответы — но фундамент всё равно здесь, поэтому роутинг разберём досконально.
CRUD-заготовка на Fiber
GOpackage main
import (
"log"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
app.Get("/notes", func(c fiber.Ctx) error {
return c.SendString("Список заметок")
})
app.Get("/notes/:id", func(c fiber.Ctx) error {
return c.SendString("Заметка " + c.Params("id"))
})
app.Post("/notes", func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusCreated)
})
app.Put("/notes/:id", func(c fiber.Ctx) error {
return c.SendString("Обновляем заметку " + c.Params("id"))
})
app.Delete("/notes/:id", func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusNoContent)
})
log.Fatal(app.Listen(":3000"))
}Разбор по строкам. app.Get("/notes") отвечает за список, app.Get("/notes/:id") — за одну заметку: двоеточие объявляет параметр пути, его значение достаётся через c.Params("id"). app.Post("/notes") возвращает 201 через c.SendStatus(fiber.StatusCreated) — коды статусов в Fiber объявлены константами вроде StatusCreated и StatusNoContent. app.Put и app.Delete — обновление и удаление; удаление отвечает 204, телесного контента у него нет. Пять вызовов — пять эндпоинтов, никаких ручных switch по путям.
Проверьте себя
Какой ответ получит клиент после POST /notes к серверу из примера?
Параметры пути: :id, дефолты и несколько сегментов#
Двоеточие в маршруте Fiber объявляет именованный параметр: он матчит ровно один сегмент пути. Значение достаётся методом c.Params("id") — всегда строка. Числа получаются через strconv.Atoi с проверкой ошибки, но об этом — в следующем уроке про параметры.
Параметров может быть несколько, и у Params есть приятная мелочь — значение по умолчанию вторым аргументом: c.Params("name", "гость"). А если добавить к сегменту вопросительный знак — :name? — параметр становится опциональным: маршрут сработает и без него. Это удобно для хвостов вроде /profile/:section? — страница профиля и её разделы живут в одном маршруте.
Два параметра и опциональный сегмент
GOapp.Get("/users/:userId/posts/:postId", func(c fiber.Ctx) error {
return c.SendString("user=" + c.Params("userId") +
" post=" + c.Params("postId"))
})
app.Get("/hello/:name?", func(c fiber.Ctx) error {
return c.SendString("Привет, " + c.Params("name", "гость"))
})В /users/:userId/posts/:postId два параметра: запрос GET /users/42/posts/7 вернёт «user=42 post=7». Имена в маршруте и ключи в Params должны совпадать — опечатка здесь не падает при компиляции, а возвращает пустую строку, так что проверяйте внимательно. Маршрут /hello/:name? благодаря вопросительному знаку сматчит и /hello — тогда Params вернёт дефолт «гость», — и /hello/bob, где name равен «bob».
Опциональность имеет значение
Зарегистрирован только маршрут app.Get("/users/:id", ...). Что получит клиент при запросе GET /users без идентификатора?
Wildcard и плюс: жадные маршруты#
Звезда в конце пути — жадный и опциональный остаток: app.Get("/files/") сматчит и /files/, и /files/docs/report.pdf. Всё, что пришло после префикса, включая слэши, достаётся через c.Params(""). Плюс ведёт себя почти так же, но обязателен: /files/+ не сматчит пустой остаток /files/. Это отличие часто всплывает, когда нужно, чтобы корень префикса отдавал отдельный ответ.
Для сравнения: в net/http с Go 1.22 аналог звезды записывается как {path...}. Синтаксис другой, идея та же — забрать весь хвост пути одним параметром.
Забираем весь хвост пути
GOapp.Get("/files/*", func(c fiber.Ctx) error {
return c.SendString("Остаток пути: " + c.Params("*"))
})
// GET /files/docs/report.pdf
// -> "Остаток пути: docs/report.pdf"Звезда поглощает остаток пути любой вложенности. Обратите внимание: ведущий слэш после /files в значение не попадает — вы получаете docs/report.pdf, а не /docs/report.pdf. На практике по этому хвосту обычно ищут файл на диске; помните про проверку пути, чтобы клиент не выбрался за пределы папки — в Fiber для раздачи файлов есть готовый app.Static, к нему отдельный урок.
Что вернёт сервер на запрос GET /shop/hello?
Прочитайте код и выберите, что он выведет:
app.Get("/shop/hello", func(c fiber.Ctx) error {
return c.SendString("Статический")
})
app.Get("/shop/:name", func(c fiber.Ctx) error {
return c.SendString("Параметр: " + c.Params("name"))
})Порядок регистрации: первый выигрывает#
Правило из прошлого примера стоит зафиксировать отдельно, потому что оно тихо ломает большие API. Fiber проверяет маршруты в том порядке, в каком вы их зарегистрировали. Конкретный путь всегда выигрывает у параметра, только если он объявлен раньше. Отсюда дисциплина больших роутеров: сначала точные пути, потом параметризованные, wildcard — последними.
Второй нюанс — регистрация дублей. Дважды объявленный app.Get("/notes") не упадёт при старте, но по маршруту пройдёт только первая запись. Это редко осознанный выбор, чаще забытый дубль после рефакторинга, — я такое ловил дважды и оба раза дольше, чем хотелось бы.
app.All, app.Add и коды 404/405#
Не всегда один маршрут — один метод. app.All("/health", ...) регистрирует хендлер на всех HTTP-методах ровно по этому пути — в отличие от app.Use, который работает по префиксу и разбирался бы с middleware. app.Add([]string{fiber.MethodGet, fiber.MethodHead}, "/ping", ...) вешает хендлер сразу на перечисленные методы — удобно для эндпоинтов, которые читают, но ничего не меняют.
Теперь про ошибки. Несуществующий путь — 404. Существующий путь с чужим методом — 405 Method Not Allowed: Fiber сам различает эти ситуации. Обе реакции переопределяются в ErrorHandler — единый формат ошибок сделаем в четырнадцатом уроке.
Все методы сразу и несколько методов
GOapp.All("/health", func(c fiber.Ctx) error {
return c.SendString("OK")
})
app.Add([]string{fiber.MethodGet, fiber.MethodHead}, "/ping",
func(c fiber.Ctx) error {
return c.SendString("pong")
})app.All применяет хендлер к любому методу по точному пути /health — healthcheck-эндпоинты так и пишутся. app.Add принимает слайс методов: fiber.MethodGet и fiber.MethodHead — константы из пакета fiber, писать "GET" строкой тоже можно, но константы не дают опечататься. Полный список методов, который понимает Fiber: Get, Head, Post, Put, Patch, Delete, Connect, Options, Trace.
Доработайте маршруты
Вставьте пропущенные фрагменты: жадный остаток пути и опциональный параметр.
Словарь урока
Честно про запуск и что дальше#
Код этого урока — готовый main.go: соберите его в одном файле и запустите локально через go run ., проверяя маршруты curl-ом; в браузерной песочнице сайта сетевые приложения не работают. Проверьте особенно порядок: перенесите /shop/hello ниже параметризованного маршрута и убедитесь, что результат изменился.
Дальше по плану — параметры запроса: c.Params лишь одна из точек, откуда приходят данные. Query-строка вида ?page=2&sort=desc разбирается методом c.Query с дефолтами и типовыми хелперами: Урок 4: параметры и query-параметры.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Маршрут в Fiber — вызов app.Get/app.Post/app.Put/app.Delete с путём и хендлером.
- :id объявляет параметр пути; значение достаётся c.Params("id") и всегда приходит строкой.
- :name? делает сегмент опциональным, у Params есть дефолт вторым аргументом.
- * — жадный опциональный остаток пути, + — жадный обязательный; оба читаются через c.Params.
- Первый сматчившийся маршрут выигрывает: точные пути регистрируйте раньше параметров и wildcard.
- Несуществующий путь — 404, существующий путь с чужим методом — 405; оба переопределяются в ErrorHandler.
Домашнее задание
Спроектируйте маршруты каталога книг: список, карточка по идентификатору, комментарии книги (/books/:id/comments), жанр с необязательным сегментом (/books/genre/:genre?) и раздача обложек (/covers/*). Зарегистрируйте их в правильном порядке и письменно объясните, почему порядок именно такой — проверьте себя переносом строк.
Личные заметкиПусто
Вопросы и ответы по уроку
Чем :id отличается от * в маршруте Fiber?
:id матчит ровно один сегмент пути, звезда * забирает весь остаток любой вложенности и читается через c.Params("*"). Для одного сегмента звезда избыточна.
Почему маршрут срабатывает и на /users, и на /users/?
По умолчанию хвостовой слэш не различается (StrictRouting выключен). Включите fiber.Config{StrictRouting: true}, если /users и /users/ должны быть разными маршрутами.
Что вернёт DELETE на GET-маршрут?
405 Method Not Allowed, если путь существует для другого метода; 404 — если пути нет вовсе. Оба ответа настраиваются в ErrorHandler приложения.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
Gin: самый популярный веб-фреймворк Go
Курс Gin для Go: маршрутизация, binding, валидация, middleware, JWT, WebSocket, тесты и деплой. 25 уроков с кодом и итоговым REST API.
- Уверенно владеть gin.Engine: маршруты, группы, версии
- Привязывать запросы к структурам (ShouldBindJSON и др.)
- Писать middleware и использовать встроенные
- Строить аутентификацию на JWT