github.com/mstephano/gqlgen-schemagen@v0.0.0-20230113041936-dd2cd4ea46aa/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/mstephano/gqlgen-schemagen" 37 ) 38 ``` 39 40 To automatically add the dependency to your `go.mod` run 41 42 ```shell 43 go mod tidy 44 ``` 45 46 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) 47 48 ```shell 49 go get -d github.com/mstephano/gqlgen@VERSION 50 ``` 51 52 ## Building the server 53 54 ### Create the project skeleton 55 56 ```shell 57 go run github.com/mstephano/gqlgen-schemagen 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 ``` 63 ├── go.mod 64 ├── go.sum 65 ├── gqlgen.yml - The gqlgen config file, knobs for controlling the generated code. 66 ├── graph 67 │ ├── generated - A package that only contains the generated runtime 68 │ │ └── generated.go 69 │ ├── model - A package for all your graph models, generated or otherwise 70 │ │ └── models_gen.go 71 │ ├── resolver.go - The root graph resolver type. This file wont get regenerated 72 │ ├── schema.graphqls - Some schema. You can split the schema into as many graphql files as you like 73 │ └── schema.resolvers.go - the resolver implementation for schema.graphql 74 └── server.go - The entry point to your app. Customize it however you see fit 75 ``` 76 77 ### Define your schema 78 79 gqlgen is a schema-first library — before writing code, you describe your API using the GraphQL 80 [Schema Definition Language](http://graphql.org/learn/schema/). By default this goes into a file called 81 `schema.graphqls` but you can break it up into as many different files as you want. 82 83 The schema that was generated for us was: 84 85 ```graphql 86 type Todo { 87 id: ID! 88 text: String! 89 done: Boolean! 90 user: User! 91 } 92 93 type User { 94 id: ID! 95 name: String! 96 } 97 98 type Query { 99 todos: [Todo!]! 100 } 101 102 input NewTodo { 103 text: String! 104 userId: String! 105 } 106 107 type Mutation { 108 createTodo(input: NewTodo!): Todo! 109 } 110 ``` 111 112 ### Implement the resolvers 113 114 When executed, gqlgen's `generate` command compares the schema file (`graph/schema.graphqls`) with the models `graph/model/*`, and, wherever it 115 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. 116 117 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 118 it was twice: 119 120 ```go 121 func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { 122 panic(fmt.Errorf("not implemented")) 123 } 124 125 func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) { 126 panic(fmt.Errorf("not implemented")) 127 } 128 ``` 129 130 We just need to implement these two methods to get our server working: 131 132 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. 133 134 ```go 135 type Resolver struct{ 136 todos []*model.Todo 137 } 138 ``` 139 140 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. 141 142 ```go 143 func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { 144 rand, _ := rand.Int(rand.Reader, big.NewInt(100)) 145 todo := &model.Todo{ 146 Text: input.Text, 147 ID: fmt.Sprintf("T%d", rand), 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 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`: 202 203 ```yml 204 # gqlgen will search for any type names in the schema in these go packages 205 # if they match it will use them, otherwise it will generate them. 206 autobind: 207 - "github.com/[username]/gqlgen-todos/graph/model" 208 ``` 209 210 And add `Todo` fields resolver config in `gqlgen.yml` to generate resolver for `user` field 211 212 ```yml 213 # This section declares type mapping between the GraphQL and go type systems 214 # 215 # The first line in each type will be used as defaults for resolver arguments and 216 # modelgen, the others will be allowed when binding to fields. Configure them to 217 # your liking 218 models: 219 ID: 220 model: 221 - github.com/mstephano/gqlgen-schemagen/graphql.ID 222 - github.com/mstephano/gqlgen-schemagen/graphql.Int 223 - github.com/mstephano/gqlgen-schemagen/graphql.Int64 224 - github.com/mstephano/gqlgen-schemagen/graphql.Int32 225 Int: 226 model: 227 - github.com/mstephano/gqlgen-schemagen/graphql.Int 228 - github.com/mstephano/gqlgen-schemagen/graphql.Int64 229 - github.com/mstephano/gqlgen-schemagen/graphql.Int32 230 Todo: 231 fields: 232 user: 233 resolver: true 234 ``` 235 236 Next, create a new file called `graph/model/todo.go` 237 238 ```go 239 package model 240 241 type Todo struct { 242 ID string `json:"id"` 243 Text string `json:"text"` 244 Done bool `json:"done"` 245 UserID string `json:"userId"` 246 User *User `json:"user"` 247 } 248 ``` 249 250 And run `go run github.com/mstephano/gqlgen-schemagen generate`. 251 252 > If you run into this error `package github.com/mstephano/gqlgen: no Go files` while executing the `generate` command above, follow the instructions in [this](https://github.com/mstephano/gqlgen-schemagen/issues/800#issuecomment-888908950) comment for a possible solution. 253 254 Now if we look in `graph/schema.resolvers.go` we can see a new resolver, lets implement it and fix `CreateTodo`. 255 256 ```go 257 func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { 258 todo := &model.Todo{ 259 Text: input.Text, 260 ID: fmt.Sprintf("T%d", rand.Int()), 261 User: &model.User{ID: input.UserID, Name: "user " + input.UserID}, 262 UserID: input.UserID, 263 } 264 r.todos = append(r.todos, todo) 265 return todo, nil 266 } 267 268 func (r *todoResolver) User(ctx context.Context, obj *model.Todo) (*model.User, error) { 269 return &model.User{ID: obj.UserID, Name: "user " + obj.UserID}, nil 270 } 271 ``` 272 273 ## Finishing touches 274 275 At the top of our `resolver.go`, between `package` and `import`, add the following line: 276 277 ```go 278 //go:generate go run github.com/mstephano/gqlgen-schemagen generate 279 ``` 280 281 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: 282 283 ```go 284 go generate ./... 285 ```