Учебный REST-сервис для управления пользователями на Go + PostgreSQL.
| Метод | Путь | Описание |
|---|---|---|
GET |
/health |
Проверка сервиса и подключения к БД |
POST |
/api/v1/users |
Создать пользователя |
GET |
/api/v1/users |
Список пользователей (пагинация) |
GET |
/api/v1/users/{id} |
Получить пользователя по ID |
PUT / PATCH |
/api/v1/users/{id} |
Обновить пользователя |
DELETE |
/api/v1/users/{id} |
Удалить пользователя |
Модель User: id (UUID), name, email (уникальный), created_at, updated_at.
- Go 1.22+
- Docker Desktop (для PostgreSQL в dev и интеграционных тестов)
docker compose up -dКонтейнер поднимает Postgres 16 на порту 5432 с базой golearn.
make migrate-upИли вручную:
export DATABASE_URL="postgres://postgres:postgres@localhost:5432/golearn?sslmode=disable"
go run ./cmd/migrate upexport DATABASE_URL="postgres://postgres:postgres@localhost:5432/golearn?sslmode=disable"
make runСервер слушает http://localhost:8080.
| Переменная | Обязательная | По умолчанию | Описание |
|---|---|---|---|
DATABASE_URL |
да | — | DSN PostgreSQL |
HTTP_PORT |
нет | 8080 |
Порт HTTP-сервера |
LOG_LEVEL |
нет | info |
Уровень логов: debug, info, warn, error |
LOG_FORMAT |
нет | text |
Формат логов: text или json |
Пример для production-подобного окружения:
export DATABASE_URL="postgres://postgres:postgres@localhost:5432/golearn?sslmode=disable"
export LOG_LEVEL=debug
export LOG_FORMAT=json
make runmake run # запуск API
make migrate-up # применить миграции
make migrate-down # откатить последнюю миграцию
make migrate-version # текущая версия схемыБазовый URL:
BASE=http://localhost:8080curl -s "$BASE/health" | jqОтвет при успехе:
{"status":"ok","database":"ok"}curl -s -X POST "$BASE/api/v1/users" \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com"}' | jqОтвет 201 Created:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Alice",
"email": "alice@example.com",
"created_at": "2026-07-07T10:00:00Z",
"updated_at": "2026-07-07T10:00:00Z"
}Сохраните id для следующих запросов:
USER_ID="<uuid из ответа>"curl -s "$BASE/api/v1/users?limit=20&offset=0" | jqПараметры пагинации:
limit— количество записей (по умолчанию 20, максимум 100)offset— смещение (по умолчанию 0)
curl -s "$BASE/api/v1/users/$USER_ID" | jqcurl -s -X PUT "$BASE/api/v1/users/$USER_ID" \
-H "Content-Type: application/json" \
-d '{"name":"Alice Updated","email":"alice.updated@example.com"}' | jqPATCH работает так же, как PUT.
curl -s -o /dev/null -w "%{http_code}\n" -X DELETE "$BASE/api/v1/users/$USER_ID"Ожидаемый ответ: 204 No Content.
| Код | Когда |
|---|---|
200 |
Успешное чтение или обновление |
201 |
Пользователь создан |
204 |
Пользователь удалён |
400 |
Невалидный JSON, email или параметры |
404 |
Пользователь не найден |
409 |
Email уже занят |
500 |
Внутренняя ошибка сервера (включая необработанную panic) |
503 |
БД недоступна (/health) |
Тело ошибки:
{"error":"user not found"}Сервис использует структурированное логирование через log/slog:
- Старт/остановка — события жизненного цикла сервера
- HTTP-запросы —
httpmiddlewareлогирует каждый запрос:request_id,method,path,status,duration,bytes - Ошибки 500 — handler пишет
slog.Errorс контекстом запроса - Panic — recover-middleware логирует stack trace и отдаёт
500с JSON{"error":"internal server error"}
Логгер собирается в main через logging.New(LOG_LEVEL, LOG_FORMAT); пакет config содержит только данные из env.
Пример лога (формат text):
level=INFO msg="request completed" request_id=abc-123 method=POST path=/api/v1/users status=201 bytes=156 duration=12.5ms remote_addr=127.0.0.1:54321
Для агрегации в ELK/Loki используйте LOG_FORMAT=json.
Защита от «зависших» клиентов и Slowloris-атак:
| Таймаут | Значение | Назначение |
|---|---|---|
ReadHeaderTimeout |
5s | Ожидание заголовков запроса |
ReadTimeout |
5s | Чтение тела запроса |
WriteTimeout |
15s | Запись ответа клиенту |
IdleTimeout |
60s | Keep-alive без активности |
| Handler timeout | 10s | Middleware: максимальное время обработки запроса |
| Shutdown timeout | 10s | Graceful shutdown по SIGINT/SIGTERM |
go test -race -count=1 ./...Интеграционные тесты используют testcontainers (Docker должен быть запущен).
cmd/api/ — точка входа
cmd/migrate/ — CLI для миграций (up/down/version)
internal/
config/ — конфигурация из env
logging/ — сборка slog-логгера
domain/ — модель User, доменные ошибки
service/ — бизнес-логика
handler/ — HTTP handlers
httperr/ — единый JSON-формат ошибок
httpmiddleware/ — slog logging, recover
repository/postgres/— SQL-репозиторий
database/ — pgx pool, миграции
mocks/ — testify-моки для unit-тестов
testdb/ — testcontainers для postgres-тестов
migrations/ — SQL-миграции (формат tern, встраиваются через go:embed)
tern.conf — конфиг tern CLI
test/integration/ — интеграционные тесты
HTTP Client → chi + httpmiddleware → Handler → Service → Repository → PostgreSQL
Зависимости направлены внутрь: handler знает service, service знает интерфейс репозитория, postgres реализует интерфейс.