Как написать REST API на Go за 30 минут: на практике

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему Go — идеальный выбор для REST API

Go (Golang) — это компилируемый язык программирования, созданный в Google для высоконагруженных систем. Он сочетает скорость C, простоту Python и встроенную поддержку параллелизма. Для создания REST API Go предлагает мощную стандартную библиотеку net/http и популярные фреймворки, такие как gorilla/mux и gin.

В этом руководстве мы за 30 минут создадим полноценный REST API для управления списком задач (Todo List) на чистом Go без сторонних фреймворков. Вы научитесь:

  • Настраивать маршрутизацию и обработчики
  • Работать с JSON (сериализация/десериализация)
  • Реализовывать CRUD-операции (Create, Read, Update, Delete)
  • Добавлять middleware для логирования и CORS

Весь код будет написан в одном файле main.go для простоты. Приступим!

Шаг 1: Базовая структура и запуск HTTP-сервера

Создайте файл main.go и добавьте следующий код. Мы объявим структуру данных для задачи и запустим простой сервер.

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "strconv"
    "strings"
    "sync"
    "time"
)

// Task — структура задачи
type Task struct {
    ID        int       `json:"id"`
    Title     string    `json:"title"`
    Completed bool      `json:"completed"`
    CreatedAt time.Time `json:"created_at"`
}

// Хранилище задач (in-memory)
var (
    tasks   = make(map[int]Task)
    nextID  = 1
    mu      sync.Mutex
)

func main() {
    // Маршруты
    http.HandleFunc("/api/tasks", tasksHandler)
    http.HandleFunc("/api/tasks/", taskByIDHandler)

    log.Println("Сервер запущен на :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Объяснение:

  • Task — структура с JSON-тегами для корректной сериализации.
  • tasks — карта для хранения задач в памяти (без БД).
  • mu — мьютекс для безопасного доступа из горутин.
  • Два маршрута: /api/tasks (список и создание) и /api/tasks/ (работа с одной задачей по ID).

Шаг 2: Обработчики для списка задач (GET и POST)

Реализуем функцию tasksHandler, которая обрабатывает GET (получение всех задач) и POST (создание новой задачи).

func tasksHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    switch r.Method {
    case http.MethodGet:
        mu.Lock()
        taskList := make([]Task, 0, len(tasks))
        for _, task := range tasks {
            taskList = append(taskList, task)
        }
        mu.Unlock()
        json.NewEncoder(w).Encode(taskList)

    case http.MethodPost:
        var newTask Task
        if err := json.NewDecoder(r.Body).Decode(&newTask); err != nil {
            http.Error(w, `{"error":"Неверный JSON"}`, http.StatusBadRequest)
            return
        }
        mu.Lock()
        newTask.ID = nextID
        nextID++
        newTask.CreatedAt = time.Now()
        tasks[newTask.ID] = newTask
        mu.Unlock()

        w.WriteHeader(http.StatusCreated)
        json.NewEncoder(w).Encode(newTask)

    default:
        http.Error(w, `{"error":"Метод не поддерживается"}`, http.StatusMethodNotAllowed)
    }
}

Ключевые моменты:

  • Используем json.NewEncoder и json.NewDecoder для работы с JSON.
  • При POST проверяем корректность JSON, присваиваем ID и возвращаем 201 Created.
  • Мьютекс защищает данные при конкурентном доступе.

Шаг 3: Обработчики для отдельной задачи (GET, PUT, DELETE)

Теперь реализуем taskByIDHandler. Извлекаем ID из URL с помощью strings.TrimPrefix.

func taskByIDHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    // Извлекаем ID из пути /api/tasks/{id}
    idStr := strings.TrimPrefix(r.URL.Path, "/api/tasks/")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, `{"error":"Некорректный ID"}`, http.StatusBadRequest)
        return
    }

    mu.Lock()
    task, exists := tasks[id]
    mu.Unlock()

    if !exists {
        http.Error(w, `{"error":"Задача не найдена"}`, http.StatusNotFound)
        return
    }

    switch r.Method {
    case http.MethodGet:
        json.NewEncoder(w).Encode(task)

    case http.MethodPut:
        var updatedTask Task
        if err := json.NewDecoder(r.Body).Decode(&updatedTask); err != nil {
            http.Error(w, `{"error":"Неверный JSON"}`, http.StatusBadRequest)
            return
        }
        // Сохраняем оригинальный ID и дату создания
        updatedTask.ID = task.ID
        updatedTask.CreatedAt = task.CreatedAt
        mu.Lock()
        tasks[id] = updatedTask
        mu.Unlock()
        json.NewEncoder(w).Encode(updatedTask)

    case http.MethodDelete:
        mu.Lock()
        delete(tasks, id)
        mu.Unlock()
        w.WriteHeader(http.StatusNoContent)

    default:
        http.Error(w, `{"error":"Метод не поддерживается"}`, http.StatusMethodNotAllowed)
    }
}

Особенности:

  • При PUT обновляем поля, но сохраняем оригинальные ID и дату создания.
  • При DELETE возвращаем 204 No Content (стандарт для успешного удаления).
  • Обработка ошибок: 400 для неверного ID, 404 если задача не найдена.

Шаг 4: Добавляем middleware (логирование и CORS)

Чтобы сделать API production-ready, добавим middleware для логирования запросов и поддержки CORS (Cross-Origin Resource Sharing). Создадим обёртку для обработчиков.

// Middleware для логирования
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("%s %s %s", r.Method, r.RequestURI, time.Since(start))
        next(w, r)
    }
}

// Middleware для CORS
func corsMiddleware(next http.HandlerFunc) http.HandlerFunc {                

Блоги

Рекомендаций по книгам