Введение: зачем Go для REST API?
Go (Golang) — современный компилируемый язык с встроенной поддержкой конкурентности и высокой производительностью. REST API на Go работает быстро, потребляет мало памяти и легко масштабируется. За 30 минут вы создадите полноценный REST сервис с GET, POST, PUT и DELETE запросами, используя только стандартную библиотеку.
В этом руководстве мы напишем API для управления списком задач (Todo app). Вы узнаете, как настроить маршрутизацию, обрабатывать JSON, добавлять middleware для логирования и правильно возвращать ошибки. Без сторонних фреймворков — только чистый Go!
1. Настройка проекта и структура
Создайте новую директорию для проекта и инициализируйте модуль Go:
mkdir go-todo-api
cd go-todo-api
go mod init todo-api
Структура проекта будет простой:
main.go— точка входа и запуск сервераhandlers.go— обработчики запросовmodels.go— структуры данныхmiddleware.go— промежуточное ПО
Создайте файл models.go с структурой задачи:
package main
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
Мы будем хранить задачи в памяти (срез tasks) для простоты. В реальном проекте используйте базу данных.
2. Маршрутизация и обработчики
Go использует встроенный пакет net/http. Создадим файл handlers.go с основными обработчиками.
2.1. GET /tasks — получение всех задач
package main
import (
"encoding/json"
"net/http"
"strconv"
)
var tasks = []Task{}
var nextID = 1
func getTasks(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(tasks)
}
2.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
}
newTask.ID = nextID
nextID++
tasks = append(tasks, newTask)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newTask)
}
2.3. PUT /tasks/{id} — обновление задачи
func updateTask(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid task ID", http.StatusBadRequest)
return
}
var updatedTask Task
if err := json.NewDecoder(r.Body).Decode(&updatedTask); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
for i, t := range tasks {
if t.ID == id {
tasks[i].Title = updatedTask.Title
tasks[i].Completed = updatedTask.Completed
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(tasks[i])
return
}
}
http.Error(w, "Task not found", http.StatusNotFound)
}
2.4. DELETE /tasks/{id} — удаление задачи
func deleteTask(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid task ID", http.StatusBadRequest)
return
}
for i, t := range tasks {
if t.ID == id {
tasks = append(tasks[:i], tasks[i+1:]...)
w.WriteHeader(http.StatusNoContent)
return
}
}
http.Error(w, "Task not found", http.StatusNotFound)
}
Обратите внимание: r.PathValue("id") доступно с Go 1.22. Если у вас более старая версия, используйте r.URL.Path или gorilla/mux.
3. Middleware для логирования
Middleware позволяют выполнять код до и после обработчика. Создадим простой логгер в middleware.go:
package main
import (
"log"
"net/http"
"time"
)
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("Started %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("Completed %s in %v", r.URL.Path, time.Since(start))
})
}
4. Главный файл main.go
Теперь объединим всё в main.go:
package main
import (
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
// Роутинг
mux.HandleFunc("GET /tasks", getTasks)
mux.HandleFunc("POST /tasks", createTask)
mux.HandleFunc("PUT /tasks/{id}", updateTask)
mux.HandleFunc("DELETE /tasks/{id}", deleteTask)
// Применяем middleware
handler := loggingMiddleware(mux)
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}
Запустите сервер:
go run .
Тестируйте с помощью curl или Postman:
# Создать задачу
curl -X POST http://localhost:8080/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Изучить Go","completed":false}'
# Получить все задачи
curl http://localhost:8080/tasks
5. Обработка ошибок и CORS
Добавим поддержку CORS для работы с фронтендом. Создайте новый middleware:
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
Об