Сценарий классический: Go установлен, папка проекта открыта в редакторе, хочется получить работающий HTTP-сервер. Напомню неудобство библиотечного курса: Echo — внешний модуль, песочница сайта его не исполняет, поэтому весь код урока запускается локально. Это и к лучшему: первый сервер — тот случай, когда живые логи стоит увидеть своими глазами. Путь такой: создаём папку, инициализируем модуль командой go mod init, подтягиваем Echo через go get и пишем main.go на двадцать строк. Дальше — по шагам, с разбором каждого решения, а не просто листингом.
Шаг 1: папка проекта и зависимости
GOmkdir hello-echo && cd hello-echo
go mod init github.com/you/hello-echo
go get github.com/labstack/echo/v4go mod init создаёт go.mod — манифест модуля с именем и версией Go; имя принято писать как путь репозитория, но для локального эксперимента подойдёт и короткое hello-echo. go get добавит зависимость в go.mod, скачает её, а контрольные суммы положит в go.sum. Тот же путь импорта — github.com/labstack/echo/v4 — затем используется в import.
Шаг 2: полный main.go
GOpackage main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.GET("/hello", func(c echo.Context) error {
return c.String(http.StatusOK, "Привет, Echo!")
})
e.Logger.Fatal(e.Start(":8080"))
}Это весь файл. Два импорта: сам Echo и его подкаталог middleware с готовыми обработками. Три содержательные строки внутри main: e.Use подключает middleware, e.GET регистрирует маршрут, e.Logger.Fatal(e.Start(...)) запускает сервер. Проверка: go run . (или go run main.go), затем в браузере — http://localhost:8080/hello.
echo.New: движок без предустановок#
Первая строка — e := echo.New() — создаёт *echo.Engine: объект, который хранит маршруты, middleware и настройки сервера. Если вы читали урок про Gin: там gin.Default() возвращал движок с уже подключёнными Logger и Recovery. У Echo философия другая — минимализм из теглая доходит и до конструктора: echo.New() даёт голый движок без единого middleware, и вы явно решаете, что подключить. Это чуть многословнее и заметно прозрачнее: увидели e.Use(middleware.Logger()) — знаете, что логи есть; не увидели — их нет. Минимум, который я ставлю в каждый проект, — две строки: Logger и Recover. Recover ловит паники в обработчиках и превращает их в ответ 500 вместо падения всего процесса: без него одна сломанная функция в одном маршруте уронит сервер целиком — а паника в одном из двадцати обработчиков, как показывает практика, случается всегда.
Маршруты: e.GET и echo.Context#
e.GET("/hello", handler) связывает метод GET и путь /hello с функцией. Сигнатура обработчика одна для всех методов: func(c echo.Context) error. echo.Context — это «руль» запроса: через него читают параметры, тело и заголовки (c.Param, c.Bind, c.Request()) и отправляют ответы (c.String, c.JSON, c.HTML). c.String(http.StatusOK, "Привет, Echo!") пишет в ответ строку с Content-Type: text/plain и возвращает ошибку записи, если клиент оборвал соединение, — её вы просто пробрасываете через return, обработкой займётся фреймворк. Для JSON той же логикой работает c.JSON: передаёте статус и данные — map, срез или структуру. Обратите внимание: gin.H в Echo нет, собираете map[string]any или отдельную структуру. На мой взгляд, структура честнее — она фиксирует формат ответа компилятором, и переименование поля ломает сборку, а не продакшен.
e.Start и e.Logger.Fatal: почему именно так#
Последняя строка — e.Logger.Fatal(e.Start(":8080")) — на первый взгляд перегружена, но каждый кусочек по делу. e.Start(":8080") внутри — это обёртка над http.ListenAndServe: сервер начинает слушать порт и блокирует выполнение до остановки; в консоль при этом падает строка «⇨ http server started on :8080». Start возвращает error, если запуститься не удалось — скажем, порт занят другим процессом. Именно поэтому Start оборачивают в e.Logger.Fatal: он печатает ошибку и завершает процесс с ненулевым кодом выхода. Без Fatal ошибка уйдёт в никуда: программа продолжит работать дальше по main() и «вежливо» завершится, а вы будете смотреть на пустой терминал, за которым никакого сервера нет. Тот же Fatal-стиль вы увидите в Gin и Fiber — это общий паттерн Go для запуска: ошибка старта означает, что продолжать нечего.
Проверяем: браузер, curl и живой лог#
Запустите go run . и откройте http://localhost:8080/hello — увидите приветствие. Сразу после старта Echo рисует ASCII-баннер с версией, а при каждом запросе middleware.Logger дописывает строку в терминал. Свежие версии (с v4.10) по умолчанию пишут лог одной JSON-строкой; сокращённо она выглядит так:
{"time":"...","level":"INFO","prefix":"echo","remote_ip":"127.0.0.1","method":"GET","uri":"/hello","status":200,"latency_human":"...","bytes_out":24}
Отсюда берут всё при отладке: remote_ip — кто пришёл, method и uri — что и куда запросил, status — что ответили, latency_human — сколько обрабатывали. Поле uri особенно полезно, когда ловите опечатку в пути: запрос, который вы считали /users/42, а на самом деле это /user/42, в логе виден мгновенно. Тот же запрос из терминала — curl -v http://localhost:8080/hello — покажет и заголовки ответа, чего браузер не всегда делает удобно.
Что вы увидите при запуске сервера
Прочитайте код и выберите, что он выведет:
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.GET("/hello", func(c echo.Context) error {
return c.String(http.StatusOK, "Привет, Echo!")
})
e.Logger.Fatal(e.Start(":8080"))
}Проверка: занятый порт
Что произойдёт при e.Logger.Fatal(e.Start(":8080")), если порт 8080 уже занят другим процессом?
Проверка: echo.New и middleware
Чем echo.New() отличается от gin.Default()?
POST-эндпоинт: принимаем JSON
GOtype noteInput struct {
Title string
Content string
}
func createNote(c echo.Context) error {
var in noteInput
if err := c.Bind(&in); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "ожидался корректный JSON")
}
return c.JSON(http.StatusCreated, map[string]string{
"title": in.Title,
"slug": strings.ToLower(strings.ReplaceAll(in.Title, " ", "-")),
})
}
// в main(): e.POST("/notes", createNote)Обработчик вынесен в отдельную функцию и передаётся по имени — так делают почти всегда, когда логики больше двух строк. c.Bind декодирует тело запроса в структуру: JSON-поля title и content совпадут с Title и Content даже без тегов — encoding/json сравнивает имена без учёта регистра; явные теги добавим в уроке про валидацию. echo.NewHTTPError — первый знакомый нам тип ошибки со статусом: вернули её — клиент получит 400 с сообщением. Статус 201 Created честно говорит, что ресурс создан. Для slug нужен импорт strings.
Заполните пропуски в main.go
Восстановите файл: подкаталог middleware, конструктор движка, подключение Logger, строковый ответ и запуск сервера.
Соберите main.go из строк
Расставьте строки в правильном порядке — от объявления пакета до запуска сервера.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Ошибка bind: address already in use означает, что порт держит другой процесс. Найдите его: lsof -i :8080 (macOS, Linux) или netstat -ano | findstr :8080 (Windows) — и остановите, либо запустите Echo на другом порту. Самый частый виновник — прошлый go run, забытый в соседнем терминале.
Что дальше по курсу#
Следующий урок — маршрутизация и параметры: path-параметры :id, query-параметры и приоритет маршрутов. Потом научимся работать с телом и заголовками (запросы и ответы), писать собственные middleware (цепочки middleware) и структурировать ошибки через HTTPError и свой ErrorHandler. Если сравнить этот main.go с gin-версией из курса Gin, разница уместится в две-три строки — и это хороший повод прочитать оба, чтобы выбрать фреймворк осознанно.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Проект на Echo: go mod init, затем go get github.com/labstack/echo/v4; путь импорта совпадает с модульным.
- echo.New() возвращает движок без middleware — Logger и Recover подключаются явно через e.Use.
- Обработчик — func(c echo.Context) error; ответы уходят через c.String, c.JSON и возвращаемую ошибку.
- e.Logger.Fatal(e.Start(":8080")): Start блокирует выполнение, Fatal гасит процесс при ошибке старта.
- Занятый порт — ошибка bind: address already in use и выход: ищите забытый go run в соседнем терминале.
- c.Bind декодирует JSON в структуру; поля матчатся без учёта регистра даже без тегов.
- Свежий Echo пишет логи в JSON (с v4.10); баннер отключается e.HideBanner = true.
Домашнее задание
Добавьте в main.go маршрут POST /echo: он через c.Bind читает структуру с полем Message и отвечает JSON {"received": ...}; ошибку привязки возвращайте через echo.NewHTTPError(http.StatusBadRequest, ...). Проверьте curl-ом два запроса: curl -X POST localhost:8080/echo -d '{"message":"privet"}' и curl -X POST localhost:8080/echo -d '{oops'. Первый должен вернуть ваш статус и тело, второй — 400 с сообщением об ошибке. Что пишет в терминал Logger на каждый из них?
Личные заметкиПусто
Вопросы и ответы по уроку
Как поменять порт Echo?
Передайте адрес в Start: e.Start(":3000") или e.Start("localhost:8080") для привязки только к локальному интерфейсу. Адрес с двоеточием в начале слушается на всех интерфейсах.
Обязательно ли оборачивать Start в e.Logger.Fatal?
Не обязательно — Start просто возвращает error. Но без Fatal программа не завершится при занятом порте и промолчит о проблеме. Для запуска в main это стандартный паттерн всех Go-фреймворков.
Что за баннер при старте и как его убрать?
ASCII-логотип Echo с версией. Отключается одним свойством до регистрации маршрутов: e.HideBanner = true.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
GORM: ORM-библиотека для Go
Курс GORM: модели, CRUD, ассоциации, транзакции, хуки, пагинация, оптимизация запросов. 25 уроков с примерами и проектом слоя данных.
- Подключать БД и настраивать пул соединений
- Описывать модели и связи: 1:1, 1:N, M:N
- Писать CRUD и сложные выборки chainable-стилем
- Контролировать N+1 через Preload