Введение: Почему 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