github.com/operandinc/gqlgen@v0.16.1/docs/content/getting-started.md (about) 1 --- 2 linkTitle: Getting Started 3 title: Building GraphQL servers in golang 4 description: Get started building type-safe GraphQL servers in Golang using gqlgen 5 menu: main 6 weight: -7 7 --- 8 9 This tutorial will take you through the process of building a GraphQL server with gqlgen that can: 10 11 - Return a list of todos 12 - Create new todos 13 - Mark off todos as they are completed 14 15 You can find the finished code for this tutorial [here](https://github.com/vektah/gqlgen-tutorials/tree/master/gettingstarted) 16 17 ## Set up Project 18 19 Create a directory for your project, and [initialise it as a Go Module](https://golang.org/doc/tutorial/create-module): 20 21 ```shell 22 mkdir gqlgen-todos 23 cd gqlgen-todos 24 go mod init github.com/[username]/gqlgen-todos 25 ``` 26 27 Next, create a `tools.go` file and add gqlgen as a [tool dependency for your module](https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module). 28 29 ```go 30 //go:build tools 31 // +build tools 32 33 package tools 34 35 import ( 36 _ "github.com/operandinc/gqlgen" 37 ) 38 ``` 39 40 To automatically add the dependency to your `go.mod` run 41 42 ```shell 43 go mod tidy 44 ``` 45 46 If you want to specify a particular version of gqlgen, you can use `go get`. For example 47 48 ```shell 49 go get -d github.com/operandinc/gqlgen 50 ``` 51 52 ## Building the server 53 54 ### Create the project skeleton 55 56 ```shell 57 go run github.com/operandinc/gqlgen init 58 printf 'package model' | gofmt > graph/model/doc.go 59 ``` 60 61 This will create our suggested package layout. You can modify these paths in gqlgen.yml if you need to. 62 63 ``` 64 ├── go.mod 65 ├── go.sum 66 ├── gqlgen.yml - The gqlgen config file, knobs for controlling the generated code. 67 ├── graph 68 │ ├── generated - A package that only contains the generated runtime 69 │ │ └── generated.go 70 │ ├── model - A package for all your graph models, generated or otherwise 71 │ │ └── models_gen.go 72 │ ├── resolver.go - The root graph resolver type. This file wont get regenerated 73 │ ├── schema.graphqls - Some schema. You can split the schema into as many graphql files as you like 74 │ └── schema.resolvers.go - the resolver implementation for schema.graphql 75 └── server.go - The entry point to your app. Customize it however you see fit 76 ``` 77 78 ### Define your schema 79 80 gqlgen is a schema-first library — before writing code, you describe your API using the GraphQL 81 [Schema Definition Language](http://graphql.org/learn/schema/). By default this goes into a file called 82 `schema.graphqls` but you can break it up into as many different files as you want. 83 84 The schema that was generated for us was: 85 86 ```graphql 87 type Todo { 88 id: ID! 89 text: String! 90 done: Boolean! 91 user: User! 92 } 93 94 type User { 95 id: ID! 96 name: String! 97 } 98 99 type Query { 100 todos: [Todo!]! 101 } 102 103 input NewTodo { 104 text: String! 105 userId: String! 106 } 107 108 type Mutation { 109 createTodo(input: NewTodo!): Todo! 110 } 111 ``` 112 113 ### Implement the resolvers 114 115 When executed, gqlgen's `generate` command compares the schema file (`graph/schema.graphqls`) with the models `graph/model/*`, and, wherever it 116 can, it will bind directly to the model. That was done already when `init` was run. We'll edit the schema later in the tutorial, but for now, let's look at what was generated already. 117 118 If we take a look in `graph/schema.resolvers.go` we will see all the times that gqlgen couldn't match them up. For us 119 it was twice: 120 121 ```go 122 func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { 123 panic(fmt.Errorf("not implemented")) 124 } 125 126 func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) { 127 panic(fmt.Errorf("not implemented")) 128 } 129 ``` 130 131 We just need to implement these two methods to get our server working: 132 133 First we need somewhere to track our state, lets put it in `graph/resolver.go`. The `graph/resolver.go` file is where we declare our app's dependencies, like our database. It gets initialized once in `server.go` when we create the graph. 134 135 ```go 136 type Resolver struct{ 137 todos []*model.Todo 138 } 139 ``` 140 141 Returning to `graph/schema.resolvers.go`, let's implement the bodies of those automatically generated resolver functions. For `CreateTodo`, we'll use `math.rand` to simply return a todo with a randomly generated ID and store that in the in-memory todos list --- in a real app, you're likely to use a database or some other backend service. 142 143 ```go 144 func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { 145 todo := &model.Todo{ 146 Text: input.Text, 147 ID: fmt.Sprintf("T%d", rand.Int()), 148 User: &model.User{ID: input.UserID, Name: "user " + input.UserID}, 149 } 150 r.todos = append(r.todos, todo) 151 return todo, nil 152 } 153 154 func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) { 155 return r.todos, nil 156 } 157 ``` 158 159 ### Run the server 160 161 We now have a working server, to start it: 162 163 ```bash 164 go run server.go 165 ``` 166 167 Open http://localhost:8080 in a browser. Here are some queries to try, starting with creating a todo: 168 169 ```graphql 170 mutation createTodo { 171 createTodo(input: { text: "todo", userId: "1" }) { 172 user { 173 id 174 } 175 text 176 done 177 } 178 } 179 ``` 180 181 And then querying for it: 182 183 ```graphql 184 query findTodos { 185 todos { 186 text 187 done 188 user { 189 name 190 } 191 } 192 } 193 ``` 194 195 ### Don't eagerly fetch the user 196 197 This example is great, but in the real world fetching most objects is expensive. We dont want to load the User on the 198 todo unless the user actually asked for it. So lets replace the generated `Todo` model with something slightly more 199 realistic. 200 201 Create a new file called `graph/model/todo.go` 202 203 ```go 204 package model 205 206 type Todo struct { 207 ID string `json:"id"` 208 Text string `json:"text"` 209 Done bool `json:"done"` 210 User *User `json:"user"` 211 } 212 ``` 213 214 > Note 215 > 216 > By default gqlgen will use any models in the model directory that match on name, this can be configured in `gqlgen.yml`. 217 218 And run `go run github.com/operandinc/gqlgen generate`. 219 220 > If you run into this error `package github.com/operandinc/gqlgen: no Go files` while executing the `generate` command above, follow the instructions in [this](https://github.com/operandinc/gqlgen/issues/800#issuecomment-888908950) comment for a possible solution. 221 222 Now if we look in `graph/schema.resolvers.go` we can see a new resolver, lets implement it and fix `CreateTodo`. 223 224 ```go 225 func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { 226 todo := &model.Todo{ 227 Text: input.Text, 228 ID: fmt.Sprintf("T%d", rand.Int()), 229 User: &model.User{ID: input.UserID, Name: "user " + input.UserID}, 230 } 231 r.todos = append(r.todos, todo) 232 return todo, nil 233 } 234 235 func (r *todoResolver) User(ctx context.Context, obj *model.Todo) (*model.User, error) { 236 return &model.User{ID: obj.UserID, Name: "user " + obj.UserID}, nil 237 } 238 ``` 239 240 ## Finishing touches 241 242 At the top of our `resolver.go`, between `package` and `import`, add the following line: 243 244 ```go 245 //go:generate go run github.com/operandinc/gqlgen generate 246 ``` 247 248 This magic comment tells `go generate` what command to run when we want to regenerate our code. To run go generate recursively over your entire project, use this command: 249 250 ```go 251 go generate ./... 252 ```