Введение: Почему Go идеален для REST API?
Go (или Golang) — это современный компилируемый язык программирования, созданный в Google. Он сочетает в себе производительность C++ и простоту Python, что делает его идеальным выбором для создания высоконагруженных веб-сервисов и REST API. В отличие от многих других языков, Go имеет встроенную поддержку HTTP-сервера, многопоточности и быструю компиляцию.
В этом руководстве мы шаг за шагом создадим полноценный REST API для управления списком задач (Todo List). Вы научитесь:
- Настраивать HTTP-сервер на Go
- Работать с JSON-запросами и ответами
- Реализовывать CRUD-операции (Create, Read, Update, Delete)
- Тестировать API с помощью curl или Postman
Весь процесс займет у вас не более 30 минут. Поехали!
1. Установка и настройка проекта
Прежде чем писать код, убедитесь, что у вас установлен Go. Проверить это можно командой:
go version
Если Go не установлен, скачайте последнюю версию с официального сайта.
Создайте новую директорию для проекта и инициализируйте модуль:
mkdir go-rest-api
cd go-rest-api
go mod init go-rest-api
Теперь создайте файл main.go — в нем будет весь наш код. Для удобства работы с маршрутами мы будем использовать стандартную библиотеку net/http, которая уже встроена в Go. Никаких сторонних зависимостей не требуется — это одно из главных преимуществ языка.
2. Определяем структуру данных и хранилище
Начнем с описания модели данных. Для простоты будем хранить задачи в памяти (в слайсе), а не в базе данных. Создадим структуру Task и глобальный счетчик ID:
package main
import (
"encoding/json"
"log"
"net/http"
"strconv"
"strings"
"sync"
)
// Task представляет задачу
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
}
// Хранилище задач (в памяти)
var (
tasks []Task
nextID int = 1
mu sync.Mutex
)
Обратите внимание на sync.Mutex — он нужен для потокобезопасного доступа к данным, так как Go-сервер может обрабатывать запросы параллельно.
3. Реализация CRUD-операций
Теперь напишем основные обработчики для каждой HTTP-методы. Мы будем использовать паттерн маршрутизации по URL:
GET /tasks— получить все задачиPOST /tasks— создать новую задачуGET /tasks/{id}— получить задачу по IDPUT /tasks/{id}— обновить задачуDELETE /tasks/{id}— удалить задачу
3.1. Получение всех задач (GET /tasks)
func getTasks(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(tasks)
}
3.2. Создание задачи (POST /tasks)
func createTask(w http.ResponseWriter, r *http.Request) {
var newTask Task
if err := json.NewDecoder(r.Body).Decode(&newTask); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
mu.Lock()
newTask.ID = nextID
nextID++
tasks = append(tasks, newTask)
mu.Unlock()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newTask)
}
3.3. Получение, обновление и удаление по ID
Для этих операций нам нужно извлечь ID из URL. В стандартной библиотеке Go нет встроенного парсера путей, поэтому мы напишем простую утилиту:
// extractID извлекает ID из URL вида /tasks/123
func extractID(path string) (int, bool) {
parts := strings.Split(strings.Trim(path, "/"), "/")
if len(parts) != 2 || parts[0] != "tasks" {
return 0, false
}
id, err := strconv.Atoi(parts[1])
if err != nil {
return 0, false
}
return id, true
}
func handleTaskByID(w http.ResponseWriter, r *http.Request) {
id, ok := extractID(r.URL.Path)
if !ok {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
mu.Lock()
defer mu.Unlock()
for i, t := range tasks {
if t.ID == id {
switch r.Method {
case http.MethodGet:
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(t)
return
case http.MethodPut:
var updated Task
if err := json.NewDecoder(r.Body).Decode(&updated); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
updated.ID = id
tasks[i] = updated
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(updated)
return
case http.MethodDelete:
tasks = append(tasks[:i], tasks[i+1:]...)
w.WriteHeader(http.StatusNoContent)
return
}
}
}
http.Error(w, "Task not found", http.StatusNotFound)
}
4. Настройка маршрутизации и запуск сервера
Теперь объединим все обработчики в единую маршрутизацию. В Go для этого используется http.HandleFunc. Мы будем различать методы внутри обработчика:
func main() {
// Маршрут для /tasks (без ID)
http.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
getTasks(w, r)
case http.MethodPost:
createTask(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
// Маршрут для /tasks/{id}
http.HandleFunc("/tasks/", handleTaskByID)
// Запуск сервера на порту 8080
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}