Введение
GraphQL — это современный язык запросов для API, разработанный Facebook. В отличие от REST, он позволяет клиенту запрашивать только необходимые данные, что снижает нагрузку на сеть и ускоряет разработку. В этом туториале мы создадим полноценный GraphQL API на языке Go (Golang) с использованием популярной библиотеки gqlgen. Вы узнаете, как настроить сервер, определить схему, написать резолверы и выполнить запросы. Статья ориентирована на разработчиков, знакомых с основами Go, но даже новички смогут повторить шаги благодаря подробным комментариям.
1. Настройка проекта и установка зависимостей
Первым делом создадим новый проект Go и установим необходимые пакеты.
Шаг 1: Инициализация модуля
mkdir graphql-go-tutorial
cd graphql-go-tutorial
go mod init graphql-go-tutorial
Шаг 2: Установка gqlgen
gqlgen — это генератор кода GraphQL для Go. Он автоматически создает резолверы на основе схемы.
go get github.com/99designs/gqlgen@latest
Шаг 3: Генерация скелета проекта
Запустите команду инициализации:
go run github.com/99designs/gqlgen init
После выполнения вы увидите структуру:
graph/— папка со схемой и резолверамиserver.go— точка входаgqlgen.yml— конфигурация генератора
2. Определение схемы GraphQL
Схема (schema) описывает типы данных и операции API. Откройте файл graph/schema.graphqls и замените его содержимое на следующее:
type User {
id: ID!
name: String!
email: String!
age: Int!
}
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!, email: String!, age: Int!): User!
updateUser(id: ID!, name: String, email: String, age: Int): User
deleteUser(id: ID!): Boolean
}
Здесь мы определили:
- User — объект с полями id, name, email, age.
- Query — операции чтения (получить всех пользователей или одного по ID).
- Mutation — операции записи (создать, обновить, удалить).
После сохранения запустите генерацию кода:
go run github.com/99designs/gqlgen generate
Это создаст файлы models_gen.go (типы Go) и generated.go (интерфейсы резолверов).
3. Реализация резолверов и хранилища данных
Теперь нужно написать логику для обработки запросов. Для простоты будем хранить данные в памяти (срез структур).
Шаг 1: Создаем хранилище
Создайте файл graph/store.go:
package graph
import "sync"
type Store struct {
mu sync.RWMutex
users []User
nextID int
}
func NewStore() *Store {
return &Store{
users: []User{
{ID: "1", Name: "Alice", Email: "alice@example.com", Age: 30},
{ID: "2", Name: "Bob", Email: "bob@example.com", Age: 25},
},
nextID: 3,
}
}
func (s *Store) GetUsers() []User {
s.mu.RLock()
defer s.mu.RUnlock()
return s.users
}
func (s *Store) GetUser(id string) *User {
s.mu.RLock()
defer s.mu.RUnlock()
for _, u := range s.users {
if u.ID == id {
return &u
}
}
return nil
}
func (s *Store) CreateUser(name, email string, age int) User {
s.mu.Lock()
defer s.mu.Unlock()
user := User{
ID: fmt.Sprintf("%d", s.nextID),
Name: name,
Email: email,
Age: age,
}
s.nextID++
s.users = append(s.users, user)
return user
}
func (s *Store) UpdateUser(id string, name, email *string, age *int) *User {
s.mu.Lock()
defer s.mu.Unlock()
for i, u := range s.users {
if u.ID == id {
if name != nil {
s.users[i].Name = *name
}
if email != nil {
s.users[i].Email = *email
}
if age != nil {
s.users[i].Age = *age
}
return &s.users[i]
}
}
return nil
}
func (s *Store) DeleteUser(id string) bool {
s.mu.Lock()
defer s.mu.Unlock()
for i, u := range s.users {
if u.ID == id {
s.users = append(s.users[:i], s.users[i+1:]...)
return true
}
}
return false
}
Не забудьте добавить импорт "fmt".
Шаг 2: Реализуем резолверы
Откройте graph/schema.resolvers.go. Там уже есть заготовки. Замените их на:
package graph
import (
"context"
"graphql-go-tutorial/graph/model"
)
func (r *mutationResolver) CreateUser(ctx context.Context, name string, email string, age int) (*model.User, error) {
user := r.Store.CreateUser(name, email, age)
return &user, nil
}
func (r *mutationResolver) UpdateUser(ctx context.Context, id string, name *string, email *string, age *int) (*model.User, error) {
user := r.Store.UpdateUser(id, name, email, age)
if user == nil {
return nil, fmt.Errorf("user not found")
}
return user, nil
}
func (r *mutationResolver) DeleteUser(ctx context.Context, id string) (bool, error) {
return r.Store.DeleteUser(id), nil
}
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
users := r.Store.GetUsers()
result := make([]*model.User, len(users))
for i, u := range users {
result[i] = &u
}
return result, nil
}
func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error) {
user := r.Store.GetUser(id)
if user == nil {
return nil, fmt.Errorf("user not found")
}
return user, nil
}