API на Go: REST, GraphQL и gRPC — что выбрать в 2025

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

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

Начать курс

Почему Go — идеальный язык для API?

Когда речь заходит о создании высоконагруженных API, Go (Golang) уверенно занимает место в топе. Его компилируемая природа, встроенная поддержка конкурентности через горутины и минималистичный синтаксис позволяют писать быстрые и надежные микросервисы. Но какой протокол выбрать для вашего следующего проекта: классический REST, гибкий GraphQL или сверхбыстрый gRPC?

В этой статье мы не просто перечислим различия — мы напишем реальный код на Go для каждого подхода, разберем бенчмарки и дадим четкие критерии выбора. Поехали!

REST API на Go: проверенная классика

REST (Representational State Transfer) остается стандартом де-факто для публичных API. Go предлагает отличную стандартную библиотеку net/http, а также популярные фреймворки вроде gin и echo.

Пример REST-сервера на чистом Go

Начнем с простого CRUD-сервиса для управления задачами. Вот как выглядит минимальный REST API на Go без сторонних зависимостей:

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "sync"
)

type Task struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

var (
    tasks   = []Task{{ID: 1, Name: "Изучить Go"}}
    mu      sync.Mutex
    nextID  = 2
)

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)
}

func createTask(w http.ResponseWriter, r *http.Request) {
    var task Task
    if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    mu.Lock()
    task.ID = nextID
    nextID++
    tasks = append(tasks, task)
    mu.Unlock()
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(task)
}

func main() {
    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)
        }
    })
    log.Println("REST API запущен на :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Плюсы REST на Go:

  • Простота и предсказуемость — каждый эндпоинт возвращает фиксированную структуру данных
  • Отличная кэшируемость через HTTP-заголовки
  • Широкая поддержка инструментов (Swagger, Postman)

Минусы: Over-fetching и under-fetching данных. Если клиенту нужно только имя задачи, он всё равно получит полный JSON.

GraphQL на Go: гибкость запросов

GraphQL решает проблему избыточности данных. Клиент сам решает, какие поля ему нужны. Go-сообщество предлагает мощную библиотеку gqlgen, которая генерирует код из схемы.

Пример GraphQL-сервера с gqlgen

Сначала определим схему schema.graphql:

type Task {
    id: ID!
    name: String!
}

type Query {
    tasks: [Task!]!
}

type Mutation {
    createTask(name: String!): Task!
}

Теперь запустим генерацию и напишем резолверы на Go:

package main

import (
    "log"
    "net/http"
    "github.com/99designs/gqlgen/graphql/handler"
    "github.com/99designs/gqlgen/graphql/playground"
)

// Предполагаем, что сгенерированный код уже есть
type Resolver struct {
    tasks []Task
    mu    sync.Mutex
}

func (r *Resolver) Tasks(ctx context.Context) ([]Task, error) {
    r.mu.Lock()
    defer r.mu.Unlock()
    return r.tasks, nil
}

func (r *Resolver) CreateTask(ctx context.Context, name string) (*Task, error) {
    r.mu.Lock()
    defer r.mu.Unlock()
    task := Task{ID: strconv.Itoa(len(r.tasks) + 1), Name: name}
    r.tasks = append(r.tasks, task)
    return &task, nil
}

func main() {
    srv := handler.NewDefaultServer(
        NewExecutableSchema(Config{Resolvers: &Resolver{}}),
    )
    http.Handle("/query", srv)
    http.Handle("/", playground.Handler("GraphQL", "/query"))
    log.Println("GraphQL API запущен на :8081")
    log.Fatal(http.ListenAndServe(":8081", nil))
}

Ключевые особенности GraphQL на Go:

  • Один эндпоинт вместо множества — весь API доступен через POST /query
  • Автоматическая генерация типов и резолверов из схемы
  • Встроенная документация через GraphQL Playground

GraphQL идеален для сложных клиентских приложений (React, Vue, мобильные приложения), где нужно точно контролировать, какие данные приходят с сервера.

gRPC на Go: скорость и строгая типизация

gRPC — это не просто API, а целый фреймворк от Google, основанный на HTTP/2 и Protobuf. Он обеспечивает бинарную сериализацию и поддержку стримов. Для Go это нативный протокол — gRPC написан на Go.

Пример gRPC-сервиса на Go

Сначала определим .proto файл:

syntax = "proto3";
package task;

service TaskService {
    rpc GetTasks (Empty) returns (TaskList);
    rpc CreateTask (CreateTaskRequest) returns (Task);
}

message Task {
    int32 id = 1;
    string name = 2;
}

message TaskList {
    repeated Task tasks = 1;
}

message CreateTaskRequest {
    string name = 1;
}

message Empty {}

Генерируем код через protoc, затем пишем сервер:

package main

import (
    "context"
    "log"
    "net"
    "google.golang.org/grpc"
    pb "path/to/your/protobuf/task"
)

type server struct {
    pb.UnimplementedTaskServiceServer
    tasks []*pb.Task
}

func (s *server) GetTasks(ctx context.Context, _ *pb.Empty) (*pb.TaskList, error) {
    return &pb.TaskList{Tasks: s.tasks}, nil
}

func (s *server) CreateTask(ctx context.Context, req *pb.CreateTaskRequest) (*pb.Task, error) {
    task := &pb.Task{
        Id:   int32(len(s.tasks) + 1),
        Name: req.Name,
    }
    s.tasks = append(s.tasks, task)
    return task, nil
}

func main() {
    lis, _ := net.Listen("tcp", ":50051")
    s := grpc.NewServer()
    pb.RegisterTaskServiceServer(s, &server{})
    log.Println("gRPC сервер запущен на :50051")
    s.Serve(lis)
}

Почему gRPC выигрывает в производительности:

  • Protobuf в 3-10 раз быстрее JSON при сериализации
  • HTTP/2 позволяет мультиплексировать запрос

Блоги

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