GraphQL API с Go: пошаговый туториал на практике

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

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

Начать курс

Введение: Почему GraphQL и Go — идеальная пара

В современной веб-разработке GraphQL стал стандартом для создания гибких и эффективных API. В сочетании с языком Go (Golang), известным своей производительностью и простотой, вы получаете мощный инструмент для бэкенд-разработки. В этом пошаговом туториале мы создадим полноценный GraphQL API на Go с нуля, используя популярную библиотеку gqlgen.

Вы узнаете, как настроить проект, определить схему, реализовать резолверы и добавить middleware для аутентификации. К концу статьи у вас будет работающий API, готовый к интеграции с любым фронтендом.

1. Настройка проекта и установка зависимостей

Первым делом создадим новый Go-модуль и установим необходимые пакеты. Убедитесь, что у вас установлен Go версии 1.18 или выше.

mkdir graphql-go-tutorial
cd graphql-go-tutorial
go mod init graphql-go-tutorial
go get github.com/99designs/gqlgen@latest
go get github.com/99designs/gqlgen/graphql/introspection@latest

Теперь инициализируем gqlgen в проекте. Эта команда создаст базовую структуру файлов:

go run github.com/99designs/gqlgen init

После выполнения вы увидите следующую структуру:

  • graph/ — директория с файлами схемы и резолверов
  • graph/schema.graphqls — определение GraphQL-схемы
  • graph/resolver.go — заглушки для резолверов
  • server.go — точка входа в приложение
  • gqlgen.yml — конфигурация генератора

2. Определение GraphQL-схемы

Откроем файл graph/schema.graphqls и определим нашу модель данных. Для примера создадим простую систему управления задачами (Task Manager):

type Task {
  id: ID!
  title: String!
  description: String
  status: TaskStatus!
  createdAt: Time!
}

enum TaskStatus {
  TODO
  IN_PROGRESS
  DONE
}

type Query {
  tasks(status: TaskStatus): [Task!]!
  task(id: ID!): Task
}

type Mutation {
  createTask(title: String!, description: String): Task!
  updateTask(id: ID!, title: String, description: String, status: TaskStatus): Task!
  deleteTask(id: ID!): Boolean!
}

scalar Time

После сохранения схемы запустим генерацию кода:

go run github.com/99designs/gqlgen generate

Эта команда создаст файлы graph/model/models_gen.go с Go-структурами и обновит резолверы.

3. Реализация резолверов и бизнес-логики

Теперь реализуем логику в файле graph/schema.resolvers.go. Сначала создадим простое хранилище в памяти (in-memory store):

// В graph/resolver.go добавим хранилище
type Resolver struct {
  tasks []*model.Task
  mu    sync.Mutex
}

func NewResolver() *Resolver {
  return &Resolver{
    tasks: make([]*model.Task, 0),
  }
}

Теперь реализуем резолверы для Query и Mutation:

// Query.Task
func (r *queryResolver) Task(ctx context.Context, id string) (*model.Task, error) {
  r.mu.Lock()
  defer r.mu.Unlock()
  
  for _, task := range r.tasks {
    if task.ID == id {
      return task, nil
    }
  }
  return nil, fmt.Errorf("task not found")
}

// Mutation.CreateTask
func (r *mutationResolver) CreateTask(ctx context.Context, title string, description *string) (*model.Task, error) {
  r.mu.Lock()
  defer r.mu.Unlock()
  
  task := &model.Task{
    ID:          uuid.New().String(),
    Title:       title,
    Description: description,
    Status:      model.TaskStatusTodo,
    CreatedAt:   time.Now(),
  }
  
  r.tasks = append(r.tasks, task)
  return task, nil
}

// Mutation.UpdateTask
func (r *mutationResolver) UpdateTask(ctx context.Context, id string, title *string, description *string, status *model.TaskStatus) (*model.Task, error) {
  r.mu.Lock()
  defer r.mu.Unlock()
  
  for _, task := range r.tasks {
    if task.ID == id {
      if title != nil {
        task.Title = *title
      }
      if description != nil {
        task.Description = description
      }
      if status != nil {
        task.Status = *status
      }
      return task, nil
    }
  }
  return nil, fmt.Errorf("task not found")
}

// Mutation.DeleteTask
func (r *mutationResolver) DeleteTask(ctx context.Context, id string) (bool, error) {
  r.mu.Lock()
  defer r.mu.Unlock()
  
  for i, task := range r.tasks {
    if task.ID == id {
      r.tasks = append(r.tasks[:i], r.tasks[i+1:]...)
      return true, nil
    }
  }
  return false, nil
}

4. Добавление middleware и запуск сервера

Для демонстрации возможностей добавим middleware для логирования и аутентификации. Обновим server.go:

package main

import (
  "log"
  "net/http"
  "time"
  
  "github.com/99designs/gqlgen/graphql/handler"
  "github.com/99designs/gqlgen/graphql/playground"
  "graphql-go-tutorial/graph"
)

// Middleware для логирования запросов
func loggingMiddleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    log.Printf("[%s] %s %s", r.Method, r.URL.Path, time.Since(start))
    next.ServeHTTP(w, r)
  })
}

// Middleware для проверки токена (упрощенно)
func authMiddleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    token := r.Header.Get("Authorization")
    if token == "" {
      http.Error(w, "Unauthorized", http.StatusUnauthorized)
      return
    }
    // Здесь должна быть проверка токена
    next.ServeHTTP(w, r)
  })
}

func main() {
  resolver := graph.NewResolver()
  srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: resolver}))
  
  // Применяем middleware
  handler := loggingMiddleware(authMiddleware(srv))
  
  http.Handle("/query", handler)
  http.Handle("/playground", playground.Handler("GraphQL Playground", "/query"))
  
  log.Println("Server started on :8080")
  log.Fatal(http.ListenAndServe(":8080", nil))
}

Запустите сервер командой go run server.go

Блоги

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