Сегодня мы будем разрабатывать приложение на Golang + GraphQL.
Мы часто используем GraphQL на своих проектах и знаем о нем немало, использовали его вместе с различными языками программирования: Javascript, Ruby и теперь руки дошли и до того чтобы попробовать связку Golang GraphQL.
О преимуществах языка запросов GraphQL в интернете сказано немало, многие хвалят его за простоту, гибкость и удобство, как при использовании на стороне сервера, так и на клиенте.
Для удобной разработки с использованием GraphQL часто настраивается GraphiQL или GraphQL Playground — интерфейс для отправки запросов и просмотра документации к API.
Чтобы развернуть такой Playground у себя локально достаточно воспользоваться этим open source решением — Golang HTTP.Handler for graphl-go.

Так выглядит запущенный playground.
Перейдем к написанию небольшого приложения на примере которого разберемся, как работать с GraphQL и Go. С полным кодом приложения можно ознакомиться по ссылке на Github.
Первым делом запустим сервер и плейграунд.
Ни один graphql backend не может обойтись без описания схемы, сейчас разберем ее описание.
Выше мы описали тип User с полями _id, firstName, lastName и email, который мы будем использовать как тип ответа на запросы. Поле _id имеет тип ObjectID, который является кастомным типом, так как было необходимо сериализовать родной тип ObjectID MongoDB, представляющий собой структуру такого вида.
Для описания входных параметров в мутацию для добавления пользователя был создан тип UserInput содержащий 3 необязательных поля, описывающих нашего юзера. Поле _id в этом типе отсутствует, так как он будет сгенерирован в резолвере.
Для подключения к mongodb в этом проекте используется golang mongodb driver.
Здесь мы обработали запрос на получение пользователя и мутацию, используя mongodb как место для хранения данных о пользователях. Вместо этой БД можно было использовать любую другую без каких-либо проблем как нереляционную, так и реляционную, ведь GraphQL никак не ограничивает нас в выборе базы данных.
Я использовал docker compose для того чтобы связать golang и mongodb. Для этого я описал небольшой файл настроек.
Все готово.
Пара технологий golang mongo позволяет нам хранить данные пользователей в удобном виде для дальнейшего возвращения их из GraphQL запросов.
Конечно, это приложение получилось довольно простым и компактным. И его можно брать за основу для вашего следующего проекта и дополнять новыми типами, запросами и мутациями и другим функционалом. В этот раз мы не рассмотрели примеры реализации еще несколько интересных возможностей GraphQL, например, subscription. Возможно, я продолжу этот пример в будущем, поэтому оставляйте комментарии, если вам чего-то не хватило в этой статье и в следующей части мы обязательно это рассмотрим.
Мы часто используем GraphQL на своих проектах и знаем о нем немало, использовали его вместе с различными языками программирования: Javascript, Ruby и теперь руки дошли и до того чтобы попробовать связку Golang GraphQL.
О преимуществах языка запросов GraphQL в интернете сказано немало, многие хвалят его за простоту, гибкость и удобство, как при использовании на стороне сервера, так и на клиенте.
Для удобной разработки с использованием GraphQL часто настраивается GraphiQL или GraphQL Playground — интерфейс для отправки запросов и просмотра документации к API.
Чтобы развернуть такой Playground у себя локально достаточно воспользоваться этим open source решением — Golang HTTP.Handler for graphl-go.

Так выглядит запущенный playground.
Перейдем к написанию небольшого приложения на примере которого разберемся, как работать с GraphQL и Go. С полным кодом приложения можно ознакомиться по ссылке на Github.
Первым делом запустим сервер и плейграунд.
func main() { schema, err := graphql.NewSchema(defineSchema()) // определение схемы рассмотрим чуть позже if err != nil { log.Panic("Error when creating the graphQL schema", err) } h := handler.New(&handler.Config{ Schema: &schema, Pretty: true, GraphiQL: false, Playground: true, }) // Здесь же есть интересный параметр FormatErrorFn - функция для форматирования ошибок http.Handle("/graphql", h) // путь для доступа к интерфейсу playground и для отправки запросов err = http.ListenAndServe(":8080", nil) if err != nil { log.Panic("Error when starting the http server", err) } }
Ни один graphql backend не может обойтись без описания схемы, сейчас разберем ее описание.
var User = graphql.NewObject( graphql.ObjectConfig{ Name: "User", Fields: graphql.Fields{ "_id": &graphql.Field{ Type: ObjectID, }, "firstName": &graphql.Field{ Type: graphql.String, }, "lastName": &graphql.Field{ Type: graphql.String, }, "email": &graphql.Field{ Type: graphql.String, }, }, }, ) var UserInput = graphql.NewInputObject( graphql.InputObjectConfig{ Name: "UserInput", Fields: graphql.InputObjectConfigFieldMap{ "firstName": &graphql.InputObjectFieldConfig{ Type: graphql.String, }, "lastName": &graphql.InputObjectFieldConfig{ Type: graphql.Int, }, "email": &graphql.InputObjectFieldConfig{ Type: graphql.String, }, }, }, ) func defineSchema() graphql.SchemaConfig { return graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "users": &graphql.Field{ Name: "users", Type: graphql.NewList(User), Resolve: usersResolver, }, }, }), Mutation: graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ "addUser": &graphql.Field{ Name: "addUser", Type: User, Resolve: addUserResolver, Args: graphql.FieldConfigArgument{ "input": &graphql.ArgumentConfig{ Type: UserInput, }, }, }, }, })} }
Выше мы описали тип User с полями _id, firstName, lastName и email, который мы будем использовать как тип ответа на запросы. Поле _id имеет тип ObjectID, который является кастомным типом, так как было необходимо сериализовать родной тип ObjectID MongoDB, представляющий собой структуру такого вида.
type InsertOneResult struct { InsertedID interface{} }
Для описания входных параметров в мутацию для добавления пользователя был создан тип UserInput содержащий 3 необязательных поля, описывающих нашего юзера. Поле _id в этом типе отсутствует, так как он будет сгенерирован в резолвере.
Для подключения к mongodb в этом проекте используется golang mongodb driver.
func usersCollection() *mongo.Collection { // функция, возвращающая коллецкию users для дальнейшего ее использования ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://mongo:27017")) if err != nil { log.Panic("Error when creating mongodb connection client", err) } collection := client.Database("testing").Collection("users") err = client.Connect(ctx) if err != nil { log.Panic("Error when connecting to mongodb", err) } return collection } // обработчик запроса на возвращение всех пользователей func usersResolver(_ graphql.ResolveParams) (interface{}, error) { ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) collection := usersCollection() result, err := collection.Find(ctx, bson.D{}) if err != nil { log.Print("Error when finding user", err) return nil, err } defer result.Close(ctx) var r []bson.M err = result.All(ctx, &r) if err != nil { log.Print("Error when reading users from cursor", err) } return r, nil } // обработчик мутации на добавление пользователя func addUserResolver(p graphql.ResolveParams) (interface{}, error) { ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) collection := usersCollection() // получаем параметры нового пользователя из мутации и создаем его, запоминаем его _id id, err := collection.InsertOne(ctx, p.Args["input"]) if err != nil { log.Print("Error when inserting user", err) return nil, err } var result bson.M // получаем только что созданного пользователя по _id err = collection.FindOne(ctx, bson.M{"_id": id.InsertedID}).Decode(&result) if err != nil { log.Print("Error when finding the inserted user by its id", err) return nil, err } return result, nil }
Здесь мы обработали запрос на получение пользователя и мутацию, используя mongodb как место для хранения данных о пользователях. Вместо этой БД можно было использовать любую другую без каких-либо проблем как нереляционную, так и реляционную, ведь GraphQL никак не ограничивает нас в выборе базы данных.
Я использовал docker compose для того чтобы связать golang и mongodb. Для этого я описал небольшой файл настроек.
version: '3'
services:
graphql:
image: golang
volumes:
- .:/go/src
command: /bin/bash -c "cd src && go run *.go"
ports:
- 8080:8080
mongo:
image: mongo
Все готово.
Пара технологий golang mongo позволяет нам хранить данные пользователей в удобном виде для дальнейшего возвращения их из GraphQL запросов.
Конечно, это приложение получилось довольно простым и компактным. И его можно брать за основу для вашего следующего проекта и дополнять новыми типами, запросами и мутациями и другим функционалом. В этот раз мы не рассмотрели примеры реализации еще несколько интересных возможностей GraphQL, например, subscription. Возможно, я продолжу этот пример в будущем, поэтому оставляйте комментарии, если вам чего-то не хватило в этой статье и в следующей части мы обязательно это рассмотрим.
