Введение

Это полное руководство по созданию веб-сервисов на Go. Мы разберём всё: от базовой структуры до продвинутых тем продакшена.

Основы HTTP и роутинга
Работа с данными и БД
Продакшен и масштабирование
📸 Визуализация концепции

Посмотри на картинку выше! Она показывает всю архитектуру веб-сервисов в фантастическом стиле: программист (ты!) управляет магическими потоками данных между различными сервисами — API Gateway, Database Realm и Auth Service. Каждая башня — это отдельный микросервис, а светящиеся сферы — JSON-ответы, летящие по сети!

📦 Базовая структура Разобрано

Минимальный веб-сервис

main.go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    responseData := map[string]string{
        "message": "Привет от Go веб-сервиса!",
        "status":  "success",
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(responseData)
}

func main() {
    http.HandleFunc("/hello", helloHandler)
    fmt.Println("Сервис запущен на порту 8080...")
    http.ListenAndServe(":8080", nil)
}

📥 Импорт пакетов Разобрано

encoding/json

Работа с JSON: кодирование и декодирование данных

fmt

Форматированный ввод-вывод (Println, Printf)

net/http

HTTP клиент и сервер, все типы для работы с веб

🎮 Handler и параметры Разобрано

Сигнатура обработчика

func helloHandler(w http.ResponseWriter, r *http.Request) {
    // w - ResponseWriter (отправка ответа)
    // r - Request (получение запроса)
}
️ Важно

Это стандартная сигнатура обработчика в Go. Вы не создаёте w и r сами — Go передаёт их автоматически при получении запроса.

🚀 Функция main() Разобрано

Точка входа

Функция main — это точка входа в программу. Go начинает выполнение с неё.

✓ ListenAndServe блокирует выполнение

Сервер работает в бесконечном цикле. Программа завершится только при нажатии Ctrl+C или ошибке.

🔀 http.HandleFunc Разобрано

Регистрация роутов

// Регистрируем маршрут
http.HandleFunc("/hello", helloHandler)

// Можно зарегистрировать много роутов
http.HandleFunc("/users", usersHandler)
http.HandleFunc("/products", productsHandler)
Без HandleFunc

Можно обойтись без неё, обрабатывая все запросы в одном handler и проверяя r.URL.Path, но это неудобно.

👂 ListenAndServe Разобрано

Два этапа работы

Listen (Слушать)

Открывает порт и начинает слушать входящие соединения

Serve (Обслуживать)

Принимает соединения и обрабатывает запросы

Можно разделить

Для graceful shutdown можно разделить на net.Listen() и server.Serve()

🛣️ Роуты Разобрано

Роут — это путь (URL), по которому клиент обращается к серверу.

Примеры роутов

  • /api/users — список пользователей
  • /api/products — список товаров
  • /health — проверка здоровья сервиса

🔖 Префикс /api/ Разобрано

Зачем нужен?
  • Разделение API и веб-интерфейса
  • Группировка и организация
  • Версионирование (/api/v1/, /api/v2/)
  • Настройка прав доступа
Это соглашение, а не требование

Можно работать и без него, если у вас только API

Слэш в конце роута Разобрано

Роут Поведение
/api/users Точное совпадение. Не ловит подпути
/api/users/ Ловит все подпути + редирект с /api/users
Важно

Роут со слэшем /api/users/ автоматически редиректит /api/users/api/users/ (HTTP 301)

🔄 Несколько сервисов Разобрано

4 варианта запуска

1. Разные порты

Каждый сервис на своём порту (8080, 8081, 8082)

// Сервис A
http.ListenAndServe(":8080", nil)

// Сервис B
http.ListenAndServe(":8081", nil)
2. Одно приложение

Все сервисы в одной программе с разными роутами

http.HandleFunc("/api/users", usersHandler)
http.HandleFunc("/api/products", productsHandler)
http.ListenAndServe(":80", nil)
3. Reverse Proxy (Nginx)

Nginx на порту 80 перенаправляет запросы на разные порты

4. Поддомены

Разные сервисы на разных поддоменах (users.site.com, products.site.com)

📊 HTTP статусы Следующее

Как правильно сообщать клиенту о результате запроса:

  • 200 OK — успех
  • 201 Created — ресурс создан
  • 400 Bad Request — ошибка клиента
  • 404 Not Found — ресурс не найден
  • 500 Internal Server Error — ошибка сервера
Пример
func getUser(w http.ResponseWriter, r *http.Request) {
    user, err := findUser(r.URL.Query().Get("id"))
    if err != nil {
        w.WriteHeader(http.StatusNotFound) // 404
        json.NewEncoder(w).Encode(map[string]string{
            "error": "User not found",
        })
        return
    }
    
    w.WriteHeader(http.StatusOK) // 200
    json.NewEncoder(w).Encode(user)
}

⚠️ Обработка ошибок Следующее

Как обрабатывать ошибки и возвращать понятные сообщения клиенту:

  • Невалидный JSON
  • База данных недоступна
  • Отсутствуют обязательные поля
  • Внутренние ошибки сервера

🔗 Параметры URL (GET) Следующее

Чтение query-параметров из URL:

// Запрос: /search?name=Алиса&age=25
func searchHandler(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("name")  // "Алиса"
    age := r.URL.Query().Get("age")     // "25"
    
    // Или все параметры
    params := r.URL.Query()
}

Прием JSON (POST) Следующее

Чтение JSON из тела запроса:

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

func createUser(w http.ResponseWriter, r *http.Request) {
    var user User
    err := json.NewDecoder(r.Body).Decode(&user)
    if err != nil {
        // Обработка ошибки
        return
    }
    
    // Сохраняем пользователя
}

🛡️ Middleware Следующее

Промежуточные обработчики для:

  • Логирования всех запросов
  • Аутентификации (проверка токенов)
  • CORS (разрешить запросы с других доменов)
  • Rate limiting (ограничение частоты)
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        log.Println(r.Method, r.URL.Path)
        next(w, r) // Вызываем следующий handler
    }
}

// Применение
http.HandleFunc("/api/users", loggingMiddleware(usersHandler))

✅ Валидация данных Следующее

Проверка входных данных:

  • Обязательные поля
  • Типы данных (число, строка, email)
  • Длина строк
  • Диапазоны чисел

📁 Структура проекта Следующее

Как организовать файлы при росте проекта:

myapp/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── handlers/
│   │   └── users.go
│   ├── models/
│   │   └── user.go
│   ── middleware/
│       └── auth.go
├── pkg/
│   └── database/
│       └── db.go
└── go.mod

💾 База данных Следующее

Работа с PostgreSQL/MySQL:

  • Подключение к БД
  • CRUD операции
  • Миграции схемы
  • Connection pooling

Аутентификация Следующее

JWT токены и авторизация:

  • Генерация токенов
  • Проверка токенов
  • Защита роутов
  • Refresh tokens

⚙️ Конфигурация Следующее

Переменные окружения:

  • Хранение паролей отдельно от кода
  • Разные конфиги для dev/staging/prod
  • .env файлы

🛑 Graceful Shutdown Следующее

Плавная остановка сервера:

  • Обработка сигнала Ctrl+C
  • Дождаться завершения текущих запросов
  • Закрытие соединений с БД

Тестирование Следующее

Unit и интеграционные тесты:

  • Тестирование handlers
  • Mock базы данных
  • Test coverage