github.com/spread-ai/gqlgen@v0.0.0-20221124102857-a6c8ef538a1d/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/spread-ai/gqlgen" 37 ) 38 ``` 39 40 To automatically add the dependency to your `go.mod` run 41 ```shell 42 go mod tidy 43 ``` 44 45 By default you'll be using the latest version of gqlgen, but if you want to specify a particular version you can use `go get` (replacing `VERSION` with the particular version desired) 46 ```shell 47 go get -d github.com/spread-ai/gqlgen@VERSION 48 ``` 49 50 51 52 ## Building the server 53 54 ### Create the project skeleton 55 56 ```shell 57 go run github.com/spread-ai/gqlgen init 58 ``` 59 60 This will create our suggested package layout. You can modify these paths in gqlgen.yml if you need to. 61 ``` 62 ├── go.mod 63 ├── go.sum 64 ├── gqlgen.yml - The gqlgen config file, knobs for controlling the generated code. 65 ├── graph 66 │ ├── generated - A package that only contains the generated runtime 67 │ │ └── generated.go 68 │ ├── model - A package for all your graph models, generated or otherwise 69 │ │ └── models_gen.go 70 │ ├── resolver.go - The root graph resolver type. This file wont get regenerated 71 │ ├── schema.graphqls - Some schema. You can split the schema into as many graphql files as you like 72 │ └── schema.resolvers.go - the resolver implementation for schema.graphql 73 └── server.go - The entry point to your app. Customize it however you see fit 74 ``` 75 76 ### Define your schema 77 78 gqlgen is a schema-first library — before writing code, you describe your API using the GraphQL 79 [Schema Definition Language](http://graphql.org/learn/schema/). By default this goes into a file called 80 `schema.graphqls` but you can break it up into as many different files as you want. 81 82 The schema that was generated for us was: 83 ```graphql 84 type Todo { 85 id: ID! 86 text: String! 87 done: Boolean! 88 user: User! 89 } 90 91 type User { 92 id: ID! 93 name: String! 94 } 95 96 type Query { 97 todos: [Todo!]! 98 } 99 100 input NewTodo { 101 text: String! 102 userId: String! 103 } 104 105 type Mutation { 106 createTodo(input: NewTodo!): Todo! 107 } 108 ``` 109 110 ### Implement the resolvers 111 112 When executed, gqlgen's `generate` command compares the schema file (`graph/schema.graphqls`) with the models `graph/model/*`, and, wherever it 113 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. 114 115 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 116 it was twice: 117 118 ```go 119 func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { 120 panic(fmt.Errorf("not implemented")) 121 } 122 123 func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) { 124 panic(fmt.Errorf("not implemented")) 125 } 126 ``` 127 128 We just need to implement these two methods to get our server working: 129 130 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. 131 132 ```go 133 type Resolver struct{ 134 todos []*model.Todo 135 } 136 ``` 137 138 Returning to `graph/schema.resolvers.go`, let's implement the bodies of those automatically generated resolver functions. For `CreateTodo`, we'll use the [`math.rand` package](https://pkg.go.dev/math/rand#Rand.Int) 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. 139 140 ```go 141 func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { 142 rand, _ := rand.Int(rand.Reader, big.NewInt(100)) 143 todo := &model.Todo{ 144 Text: input.Text, 145 ID: fmt.Sprintf("T%d", rand), 146 User: &model.User{ID: input.UserID, Name: "user " + input.UserID}, 147 } 148 r.todos = append(r.todos, todo) 149 return todo, nil 150 } 151 152 func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) { 153 return r.todos, nil 154 } 155 ``` 156 157 ### Run the server 158 159 We now have a working server, to start it: 160 ```bash 161 go run server.go 162 ``` 163 164 Open http://localhost:8080 in a browser. Here are some queries to try, starting with creating a todo: 165 ```graphql 166 mutation createTodo { 167 createTodo(input: { text: "todo", userId: "1" }) { 168 user { 169 id 170 } 171 text 172 done 173 } 174 } 175 ``` 176 177 And then querying for it: 178 179 ```graphql 180 query findTodos { 181 todos { 182 text 183 done 184 user { 185 name 186 } 187 } 188 } 189 ``` 190 191 ### Don't eagerly fetch the user 192 193 This example is great, but in the real world fetching most objects is expensive. We dont want to load the User on the 194 todo unless the user actually asked for it. So lets replace the generated `Todo` model with something slightly more 195 realistic. 196 197 First let's enable `autobind`, allowing gqlgen to use your custom models if it can find them rather than generating them. We do this by uncommenting the `autobind` config line in `gqlgen.yml`: 198 199 ```yml 200 # gqlgen will search for any type names in the schema in these go packages 201 # if they match it will use them, otherwise it will generate them. 202 autobind: 203 - "github.com/[username]/gqlgen-todos/graph/model" 204 ``` 205 206 And add `Todo` fields resolver config in `gqlgen.yml` to generate resolver for `user` field 207 ```yml 208 # This section declares type mapping between the GraphQL and go type systems 209 # 210 # The first line in each type will be used as defaults for resolver arguments and 211 # modelgen, the others will be allowed when binding to fields. Configure them to 212 # your liking 213 models: 214 ID: 215 model: 216 - github.com/spread-ai/gqlgen/graphql.ID 217 - github.com/spread-ai/gqlgen/graphql.Int 218 - github.com/spread-ai/gqlgen/graphql.Int64 219 - github.com/spread-ai/gqlgen/graphql.Int32 220 Int: 221 model: 222 - github.com/spread-ai/gqlgen/graphql.Int 223 - github.com/spread-ai/gqlgen/graphql.Int64 224 - github.com/spread-ai/gqlgen/graphql.Int32 225 Todo: 226 fields: 227 user: 228 resolver: true 229 ``` 230 231 Next, create a new file called `graph/model/todo.go` 232 233 ```go 234 package model 235 236 type Todo struct { 237 ID string `json:"id"` 238 Text string `json:"text"` 239 Done bool `json:"done"` 240 UserID string `json:"userId"` 241 User *User `json:"user"` 242 } 243 ``` 244 245 And run `go run github.com/spread-ai/gqlgen generate`. 246 247 > 248 > If you run into this error `package github.com/spread-ai/gqlgen: no Go files` while executing the `generate` command above, follow the instructions in [this](https://github.com/spread-ai/gqlgen/issues/800#issuecomment-888908950) comment for a possible solution. 249 250 Now if we look in `graph/schema.resolvers.go` we can see a new resolver, lets implement it and fix `CreateTodo`. 251 ```go 252 func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { 253 todo := &model.Todo{ 254 Text: input.Text, 255 ID: fmt.Sprintf("T%d", rand.Int()), 256 User: &model.User{ID: input.UserID, Name: "user " + input.UserID}, 257 UserID: input.UserID, 258 } 259 r.todos = append(r.todos, todo) 260 return todo, nil 261 } 262 263 func (r *todoResolver) User(ctx context.Context, obj *model.Todo) (*model.User, error) { 264 return &model.User{ID: obj.UserID, Name: "user " + obj.UserID}, nil 265 } 266 ``` 267 268 ## Finishing touches 269 270 At the top of our `resolver.go`, between `package` and `import`, add the following line: 271 272 ```go 273 //go:generate go run github.com/spread-ai/gqlgen generate 274 ``` 275 276 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: 277 278 ```go 279 go generate ./... 280 ```