我尝试创建了一个用Go编写的支持GraphQL的服务器

由於我在 Go 中嘗試了 GraphQL 的伺服器端實現,所以在這裡留下了一篇筆記。

准备

我們試著使用 vektah/gqlgen 這個函式庫來快速建立一個嚴格型別的 GraphQL 伺服器。

$ go get -u github.com/vektah/gqlgen

实施

首先,在项目目录下创建./schema.graphql文件。
该文件将包括User类型定义、Query/Mutation定义以及输入参数定义。

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

type Query {
    user(id: String!): User
    users: [User!]!
}

input NewUser {
    name: String!
}

type Mutation {
    createUser(input: NewUser!): User!
}

接下来,创建一个名为graph的目录,并创建以下两个文件。

package graph

type User struct {
    ID   string
    Name string
}
{
  "User": "github.com/s-ichikawa/gql-todo/graph.User"
}

既经过此步骤定义了架构,请执行以下命令。

$ cd path/to/project/graph
$ gqlgen -typemap type.json -schema ../schema.graphql

生成的.go和model_get.go文件将出现在graph目录中。这些文件是根据架构定义生成的GraphQL服务器的代码。由于代码很长,我将省略其内容,但重要的是generated.go文件的Resolver接口。

type Resolvers interface {
    Mutation_createUser(ctx context.Context, input NewUser) (User, error)
    Query_user(ctx context.Context, id string) (*User, error)
    Query_users(ctx context.Context) ([]User, error)
}

接下来将实施这一计划。

package graph

import (
    "context"
    "fmt"
    "math/rand"
    "github.com/pkg/errors"
)

type User struct {
    ID   string
    Name string
}

type Resolver struct {
    users []User
}

func (r *Resolver) Mutation_createUser(ctx context.Context, input NewUser) (User, error) {
    user := User{
        ID:   fmt.Sprintf("%d", rand.Int()),
        Name: input.Name,
    }
    r.users = append(r.users, user)
    return user, nil
}

func (r *Resolver) Query_user(ctx context.Context, id string) (*User, error) {
    for _, user := range r.users {
        if (user.ID == id) {
            return &user, nil
        }
    }
    return &User{}, errors.New("Sorry, Not Found.")
}

func (r *Resolver) Query_users(ctx context.Context) ([]User, error) {
    return r.users, nil
}

接下来创建main.go
/通过该路径可以打开一个可以执行Playground查询的页面的端点
/query是接收GraphQL的端点

package main

import (
    "github.com/s-ichikawa/gql-todo/graph"
    "net/http"
    "github.com/vektah/gqlgen/handler"
    "fmt"
    "log"
)

func main() {
    resolvers := &graph.Resolver{}
    http.Handle("/", handler.Playground("Todo", "/query"))
    http.Handle("/query", handler.GraphQL(graph.MakeExecutableSchema(resolvers)))

    fmt.Println("Listening on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

尝试运行一下

$ go run main.go 
Listening on :8080
スクリーンショット 2018-06-30 19.27.11.png
スクリーンショット 2018-06-30 19.27.45.png
スクリーンショット 2018-06-30 19.26.41.png
スクリーンショット 2018-06-30 19.36.14.png
スクリーンショット 2018-06-30 19.37.17.png

我能够以非常简单的方式实现一个能够使用GraphQL的服务器。如果将用户的保存和获取从数据库中进行,那么graph.go文件中的r.users = append(r.users, user)和for _, user := range r.users的部分将被替换为SQL查询。

这次暂时不处理DB等内容,而是稍微玩一下模式定义,试试看能否注册与用户相关的待办事项。

定义用于Todo的类型和Query/Mutation。

+ type Todo {
+     id: ID!
+     text: String!
+     done: Boolean!
+     user: User!
+ }

type Query {
    user(id: String!): User
    users: [User!]!
+     todo(id: String!): Todo!
+     todos: [Todo!]!
}

+ input NewTodo {
+     text: String!
+     userId: String!
+ }

type Mutation {
    createUser(input: NewUser!): User!
+    createTodo(input: NewTodo!): Todo!
}

在type.json中添加Todo的定义

{
  "User": "github.com/s-ichikawa/gql-todo/graph.User",
  "Todo": "github.com/s-ichikawa/gql-todo/graph.Todo"
}

在graph.go中添加Todo类型。

type Todo struct {
    ID string
    Text string
    UserId string
}

请再次运行 gqlgen 命令

$ cd path/to/project/graph
$ gqlgen -typemap type.json -schema ../schema.graphql

实现了一个添加的 Resolver(由于很长,所以只记录添加的部分)
与 User 相比几乎没有变化,但添加了一个解决 Schema 定义中的 `try Todo {…, user: User!}` 的 `func Todo_user`。

func (r *Resolver) Mutation_createTodo(ctx context.Context, input NewTodo) (Todo, error) {
    todo := Todo{
        ID:     fmt.Sprintf("T%d", rand.Int()),
        UserId: fmt.Sprintf("U%d", input.UserId),
        Text:   input.Text,
    }
    r.todos = append(r.todos, todo)
    return todo, nil
}

func (r *Resolver) Query_todo(ctx context.Context, id string) (Todo, error)  {
    for _, todo := range r.todos {
        if todo.ID == id {
            return todo, nil
        }
    }
    return Todo{}, errors.New("Not Found")
}

func (r *Resolver) Query_todos(ctx context.Context) ([]Todo, error) {
    return r.todos, nil
}

func (r *Resolver) Todo_user(ctx context.Context, obj *Todo) (User, error) {
    for _, user := range r.users {
        if (user.ID == obj.UserId) {
            return user, nil
        }
    }
    return User{}, errors.New("Sorry, Not Found.")
}

重新启动 main.go 并尝试重新发出查询。

スクリーンショット 2018-06-30 20.23.25.png

已成功获取到Todo以及其相关联的用户信息。

スクリーンショット 2018-06-30 20.24.00.png
スクリーンショット 2018-06-30 20.33.19.png

所以,因为成功获取了与某个类型相关的另一个类型,所以先到此为止。

添加附注

不过顺便说一下,当我运行 main.go 的时候出现了以下这样的错误:
$ go get github.com/gorilla/websocket,所以我还是记下来。

$ go run main.go 
../../vektah/gqlgen/handler/graphql.go:10:2: cannot find package "github.com/gorilla/websocket" in any of:
...
广告
将在 10 秒后关闭
bannerAds