Введение: почему 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 {