Leon's Blogging

Coding blogging for hackers.

Golang - GraphQL With Gqlgen

| Comments

Setup Project

使用 Go Module 記得先執行 export GO111MODULE=on

1
2
3
mkdir gqlgen-todos
cd gqlgen-todos
go mod init github.com/[username]/gqlgen-todos

Define the schema

新增 schema.graphql 定義 graphql 結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Todo {
  id: ID!
  text: String!
  done: Boolean!
  user: User!
}

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

type Query {
  todos: [Todo!]!
}

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

type Mutation {
  createTodo(input: NewTodo!): Todo!
}

Create the project skeleton

透過 gglgen 建立 graphql 的專案初始化

1
go run github.com/99designs/gqlgen init
  • gqlgen.yml — The gqlgen config file, knobs for controlling the generated code.
  • generated.go — The GraphQL execution runtime, the bulk of the generated code.
  • models_gen.go — Generated models required to build the graph. Often you will override these with your own models. Still very useful for input types.
  • resolver.go — This is where your application code lives. generated.go will call into this to get the data the user has requested.
  • server/server.go — This is a minimal entry point that sets up an http.Handler to the generated GraphQL server.

Create the database models

自動生成的 Todo model 不是正確的,因為 Todo 裡面還嵌入了 User,我們希望是在使用者要求時才給予,因此要另外建立 todo.go

1
2
3
4
5
6
7
8
9
// todo.go
package gqlgen_todos

type Todo struct {
  ID     string
  Text   string
  Done   bool
  UserID string
}

將新的 struct 路徑加到 gqlgen.yml

1
2
3
4
5
// Todo 對應到 todo.go 裡的 struct name
models:
  Todo:
    model: github.com/[username]/gqlgen-todos.Todo

重新生成檔案

1
go run github.com/99designs/gqlgen

Implement the resolvers

generated.go 產生了很多個 resolvers interface,接著就要實作這些,但 resolver.go 這個檔案,再新增新的 method 時,要重新再產生一份

1
2
rm resolver.go
go run github.com/99designs/gqlgen

實作 resolver.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package gqlgen_todos

import (
  context "context"
  "fmt"
  "math/rand"
)

type Resolver struct {
  todos []Todo
}

func (r *Resolver) Mutation() MutationResolver {
  return &mutationResolver{r}
}
func (r *Resolver) Query() QueryResolver {
  return &queryResolver{r}
}
func (r *Resolver) Todo() TodoResolver {
  return &todoResolver{r}
}

type mutationResolver struct{ *Resolver }

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

type queryResolver struct{ *Resolver }

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

type todoResolver struct{ *Resolver }

func (r *todoResolver) User(ctx context.Context, obj *Todo) (*User, error) {
  return &User{ID: obj.UserID, Name: "user " + obj.UserID}, nil
}

Query

1
2
// open http://localhost:8080
go run server/server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// create
mutation createTodo {
  createTodo(input:{text:"todo", userId:"1"}) {
    user {
      id
    }
    text
    done
  }
}

// show
query findTodos {
      todos {
      text
      done
      user {
        name
      }
    }
}

Finishing touches

At the top of our resolver.go add the following line:

1
//go:generate go run github.com/99designs/gqlgen

This magic comment tells go generate what command to run when we want to regenerate our code

Example

Reference

Comments