Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 2 из 25 ~18 мин +50 XP

Первый сервер на Echo: от go mod init до живых логов

Первый сервер на Echo поднимается за десять строк, и в этом уроке мы пройдём их все: go mod init, go get для зависимостей, echo.New, маршруты GET и POST, e.Start. Разберём каждую строку по слогам — от подключения middleware.Logger до обработки занятого порта — и получим hello world, который реально запускается.

Содержание урока · 8 разделов

Сценарий классический: Go установлен, папка проекта открыта в редакторе, хочется получить работающий HTTP-сервер. Напомню неудобство библиотечного курса: Echo — внешний модуль, песочница сайта его не исполняет, поэтому весь код урока запускается локально. Это и к лучшему: первый сервер — тот случай, когда живые логи стоит увидеть своими глазами. Путь такой: создаём папку, инициализируем модуль командой go mod init, подтягиваем Echo через go get и пишем main.go на двадцать строк. Дальше — по шагам, с разбором каждого решения, а не просто листингом.

Шаг 1: папка проекта и зависимости

GO
mkdir hello-echo && cd hello-echo
go mod init github.com/you/hello-echo
go get github.com/labstack/echo/v4

go mod init создаёт go.mod — манифест модуля с именем и версией Go; имя принято писать как путь репозитория, но для локального эксперимента подойдёт и короткое hello-echo. go get добавит зависимость в go.mod, скачает её, а контрольные суммы положит в go.sum. Тот же путь импорта — github.com/labstack/echo/v4 — затем используется в import.

Шаг 2: полный main.go

GO
package 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

GO
type 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, строковый ответ и запуск сервера.

package main import ( "net/http" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/" ) func main() { e := echo.() e.(middleware.Logger()) e.Use(middleware.Recover()) e.GET("/hello", func(c echo.Context) error { return c.(http.StatusOK, "Привет, Echo!") }) e.Logger.(e.(":8080")) }

Соберите main.go из строк

Расставьте строки в правильном порядке — от объявления пакета до запуска сервера.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Что дальше по курсу#

Следующий урок — маршрутизация и параметры: 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 · Первый сервер на Echo: от go mod init до живых логов)

Вопросы и ответы по уроку

Как поменять порт Echo?

Передайте адрес в Start: e.Start(":3000") или e.Start("localhost:8080") для привязки только к локальному интерфейсу. Адрес с двоеточием в начале слушается на всех интерфейсах.

Обязательно ли оборачивать Start в e.Logger.Fatal?

Не обязательно — Start просто возвращает error. Но без Fatal программа не завершится при занятом порте и промолчит о проблеме. Для запуска в main это стандартный паттерн всех Go-фреймворков.

Что за баннер при старте и как его убрать?

ASCII-логотип Echo с версией. Отключается одним свойством до регистрации маршрутов: e.HideBanner = true.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

GORM25 уроков~14 чпрактика25 уроков уже доступны

GORM: ORM-библиотека для Go

Курс GORM: модели, CRUD, ассоциации, транзакции, хуки, пагинация, оптимизация запросов. 25 уроков с примерами и проектом слоя данных.

  • Подключать БД и настраивать пул соединений
  • Описывать модели и связи: 1:1, 1:N, M:N
  • Писать CRUD и сложные выборки chainable-стилем
  • Контролировать N+1 через Preload