Как написать REST API на Go за 30 минут: руководство для начинающих

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

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

Начать курс

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

Go (или Golang) — это компилируемый язык программирования от Google, который сочетает производительность C++ с простотой Python. Если вы хотите создать быстрый, надёжный и легко поддерживаемый REST API, Go — один из лучших вариантов. В этой статье мы за 30 минут напишем полноценный REST API на Go, который умеет создавать, читать, обновлять и удалять записи (CRUD). Никаких сложных фреймворков — только стандартная библиотека net/http и немного кода.

К концу статьи вы получите готовый HTTP-сервер, который отвечает на запросы в формате JSON и работает без внешних зависимостей. Поехали!

Шаг 1: Настройка проекта и структура

Для начала убедитесь, что у вас установлен Go (версия 1.16+). Создайте новую директорию для проекта и инициализируйте модуль:

mkdir go-rest-api
cd go-rest-api
go mod init go-rest-api

Структура проекта будет простой — один файл main.go. Весь код мы разместим в нём, чтобы не усложнять пример. В реальных проектах вы, конечно, разобьёте код на пакеты, но для обучения это идеальный вариант.

Шаг 2: Определяем структуру данных и глобальное хранилище

Наше API будет управлять списком книг. Создадим структуру Book и простой срез для хранения данных в памяти (вместо базы данных).

package main

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

type Book struct {
    ID     int    `json:"id"`
    Title  string `json:"title"`
    Author string `json:"author"`
    Year   int    `json:"year"`
}

var books []Book
var nextID int = 1

Здесь books — наш временный «слой данных», а nextID — счётчик для автоматического присвоения ID новым книгам.

Шаг 3: Реализуем обработчики HTTP-запросов (CRUD)

Теперь самое интересное — напишем функции-обработчики для каждого HTTP-метода. Мы будем использовать стандартный маршрутизатор http.NewServeMux.

3.1. Получение всех книг (GET /books)

func getBooks(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(books)
}

3.2. Создание новой книги (POST /books)

func createBook(w http.ResponseWriter, r *http.Request) {
    var newBook Book
    if err := json.NewDecoder(r.Body).Decode(&newBook); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    newBook.ID = nextID
    nextID++
    books = append(books, newBook)
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(newBook)
}

3.3. Получение одной книги по ID (GET /books/{id})

В стандартном ServeMux нет поддержки динамических путей, поэтому мы извлечём ID из URL вручную.

func getBook(w http.ResponseWriter, r *http.Request) {
    idStr := strings.TrimPrefix(r.URL.Path, "/books/")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }
    for _, book := range books {
        if book.ID == id {
            w.Header().Set("Content-Type", "application/json")
            json.NewEncoder(w).Encode(book)
            return
        }
    }
    http.Error(w, "Book not found", http.StatusNotFound)
}

3.4. Обновление книги (PUT /books/{id})

func updateBook(w http.ResponseWriter, r *http.Request) {
    idStr := strings.TrimPrefix(r.URL.Path, "/books/")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }
    var updatedBook Book
    if err := json.NewDecoder(r.Body).Decode(&updatedBook); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    for i, book := range books {
        if book.ID == id {
            updatedBook.ID = id
            books[i] = updatedBook
            w.Header().Set("Content-Type", "application/json")
            json.NewEncoder(w).Encode(updatedBook)
            return
        }
    }
    http.Error(w, "Book not found", http.StatusNotFound)
}

3.5. Удаление книги (DELETE /books/{id})

func deleteBook(w http.ResponseWriter, r *http.Request) {
    idStr := strings.TrimPrefix(r.URL.Path, "/books/")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }
    for i, book := range books {
        if book.ID == id {
            books = append(books[:i], books[i+1:]...)
            w.WriteHeader(http.StatusNoContent)
            return
        }
    }
    http.Error(w, "Book not found", http.StatusNotFound)
}

Шаг 4: Собираем всё вместе — главная функция и маршрутизация

Теперь объединим обработчики в единый маршрутизатор. Обратите внимание на трюк: мы используем один обработчик для пути /books/, который сам распределяет запросы по методам.

func booksRouter(w http.ResponseWriter, r *http.Request) {
    // Если путь ровно "/books" — работаем со списком
    if r.URL.Path == "/books" {
        switch r.Method {
        case http.MethodGet:
            getBooks(w, r)
        case http.MethodPost:
            createBook(w, r)
        default:
            http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        }
        return
    }
    // Иначе — работаем с конкретной книгой (путь /books/{id})
    switch r.Method {
    case http.MethodGet:
        getBook(w, r)
    case http.MethodPut:
        updateBook(w, r)
    case http.MethodDelete:
        deleteBook(w, r)
    default:
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}

func main() {
    // Добавим пару тестовых книг
    books = append(books, Book{ID: 1, Title: "Война и мир", Author: "Лев Толстой", Year: 1869})
    books = append(books, Book{ID: 2, Title: "Преступление и наказание", Author: "Фёдор Достоевский", Year: 1866})
    nextID                

Блоги

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