diff --git a/ambient.go b/ambient.go index 3ec2601cc91..350bc75cc6a 100644 --- a/ambient.go +++ b/ambient.go @@ -1,4 +1,4 @@ -package gqlgen +package main import ( // Import and ignore the ambient imports listed below so dependency managers diff --git a/generate.go b/api/generate.go similarity index 99% rename from generate.go rename to api/generate.go index 86c64b7a12b..3dd083f52f6 100644 --- a/generate.go +++ b/api/generate.go @@ -1,4 +1,4 @@ -package gqlgen +package api import ( "syscall" diff --git a/option.go b/api/option.go similarity index 96% rename from option.go rename to api/option.go index 0c173c7264e..f7ba6774bd0 100644 --- a/option.go +++ b/api/option.go @@ -1,4 +1,4 @@ -package gqlgen +package api import ( "github.com/99designs/gqlgen/codegen/config" diff --git a/cmd/gen.go b/cmd/gen.go index 6a077ec97e4..5613ee31ee9 100644 --- a/cmd/gen.go +++ b/cmd/gen.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/99designs/gqlgen" + "github.com/99designs/gqlgen/api" "github.com/99designs/gqlgen/codegen/config" "github.com/pkg/errors" "github.com/urfave/cli" @@ -36,7 +36,7 @@ var genCmd = cli.Command{ } } - if err = gqlgen.Generate(cfg); err != nil { + if err = api.Generate(cfg); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(3) } diff --git a/cmd/init.go b/cmd/init.go index 076d7d20a11..664c523b780 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -7,10 +7,9 @@ import ( "os" "strings" + "github.com/99designs/gqlgen/api" "github.com/99designs/gqlgen/plugin/servergen" - "github.com/99designs/gqlgen" - "github.com/99designs/gqlgen/codegen/config" "github.com/pkg/errors" "github.com/urfave/cli" @@ -73,7 +72,7 @@ var initCmd = cli.Command{ } func GenerateGraphServer(cfg *config.Config, serverFilename string) { - err := gqlgen.Generate(cfg, gqlgen.AddPlugin(servergen.New(serverFilename))) + err := api.Generate(cfg, api.AddPlugin(servergen.New(serverFilename))) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) } diff --git a/docs/content/config.md b/docs/content/config.md index 93118c4f682..98cb7127e8b 100644 --- a/docs/content/config.md +++ b/docs/content/config.md @@ -3,7 +3,7 @@ linkTitle: Configuration title: How to configure gqlgen using gqlgen.yml description: How to configure gqlgen using gqlgen.yml menu: main -weight: -7 +weight: -5 --- gqlgen can be configured using a `gqlgen.yml` file, by default it will be loaded from the current directory, or any parent directory. diff --git a/docs/content/getting-started-dep.md b/docs/content/getting-started-dep.md new file mode 100644 index 00000000000..93c8410c370 --- /dev/null +++ b/docs/content/getting-started-dep.md @@ -0,0 +1,290 @@ +--- +linkTitle: Getting Started Using dep +title: Building GraphQL servers in golang +description: Get started building type-safe GraphQL servers in Golang using gqlgen +weight: -7 +hidden: true +--- + +> Deprecated +> +> This tutorial uses the `dep` tool to manage dependencies instead of Go Modules and should be considered a deprecated way to use gqlgen. Read out new [Getting Started]({{< ref "getting-started.md" >}}) guide for instructions for using Go Modules. + +This tutorial will take you through the process of building a GraphQL server with gqlgen that can: + + - Return a list of todos + - Create new todos + - Mark off todos as they are completed + +You can find the finished code for this tutorial [here](https://github.com/vektah/gqlgen-tutorials/tree/master/gettingstarted) + +## Install gqlgen + +This article uses [`dep`](https://github.com/golang/dep) to install gqlgen. [Follow the instructions for your environment](https://github.com/golang/dep) to install. + +Assuming you already have a working [Go environment](https://golang.org/doc/install), create a directory for the project in your `$GOPATH`: + +```sh +$ mkdir -p $GOPATH/src/github.com/[username]/gqlgen-todos +``` + +Add the following file to your project under `scripts/gqlgen.go`: + +```go +// +build ignore + +package main + +import "github.com/99designs/gqlgen/cmd" + +func main() { + cmd.Execute() +} +``` + +Lastly, initialise dep. This will inspect any imports you have in your project, and pull down the latest tagged release. + +```sh +$ dep init +``` + +## Building the server + +### Define the schema + +gqlgen is a schema-first library — before writing code, you describe your API using the GraphQL +[Schema Definition Language](http://graphql.org/learn/schema/). This usually goes into a file called `schema.graphql`: + +```graphql +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 + +```bash +$ go run scripts/gqlgen.go init +``` + +This has created an empty skeleton with all files you need: + + - `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. + + Now run dep ensure, so that we can ensure that the newly generated code's dependencies are all present: + + ```sh + $ dep ensure + ``` + +### Create the database models + +The generated model for Todo isn't right, it has a user embeded in it but we only want to fetch it if the user actually requested it. So instead lets make a new model in `todo.go`: + +```go +package gettingstarted + +type Todo struct { + ID string + Text string + Done bool + UserID string +} +``` + +Next tell gqlgen to use this new struct by adding it to `gqlgen.yml`: + +```yaml +models: + Todo: + model: github.com/[username]/gqlgen-todos/gettingstarted.Todo +``` + +Regenerate by running: + +```bash +$ go run scripts/gqlgen.go -v +Unable to bind Todo.user to github.com/[username]/gqlgen-todos/gettingstarted.Todo + no method named user + no field named user + Adding resolver method +``` + +> Note +> +> The verbose flag `-v` is here to show what gqlgen is doing. It has looked at all the fields on the model and found matching methods for all of them, except user. For user it has added a resolver to the interface you need to implement. *This is the magic that makes gqlgen work so well!* + +### Implement the resolvers + +The generated runtime has defined an interface for all the missing resolvers that we need to provide. Lets take a look in `generated.go` + +```go +// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. +func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { + return &executableSchema{ + resolvers: cfg.Resolvers, + directives: cfg.Directives, + } +} + +type Config struct { + Resolvers ResolverRoot + Directives DirectiveRoot +} + +type ResolverRoot interface { + Mutation() MutationResolver + Query() QueryResolver + Todo() TodoResolver +} + +type DirectiveRoot struct { +} +type MutationResolver interface { + CreateTodo(ctx context.Context, input NewTodo) (Todo, error) +} +type QueryResolver interface { + Todos(ctx context.Context) ([]Todo, error) +} +type TodoResolver interface { + User(ctx context.Context, obj *Todo) (User, error) +} +``` + +Notice the `TodoResolver.User` method? Thats gqlgen saying "I dont know how to get a User from a Todo, you tell me.". +Its worked out how to build everything else for us. + +For any missing models (like NewTodo) gqlgen will generate a go struct. This is usually only used for input types and +one-off return values. Most of the time your types will be coming from the database, or an API client so binding is +better than generating. + +### Write the resolvers + +This is a work in progress, we have a way to generate resolver stubs, but it cannot currently update existing code. We can force it to run again by deleting `resolver.go` and re-running gqlgen: + +```bash +$ rm resolver.go +$ go run scripts/gqlgen.go +``` + +Now we just need to fill in the `not implemented` parts. Update `resolver.go` + +```go +package gettingstarted + +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 +} + +``` + +We now have a working server, to start it: +```bash +go run server/server.go +``` + +then open http://localhost:8080 in a browser. here are some queries to try: +```graphql +mutation createTodo { + createTodo(input:{text:"todo", userId:"1"}) { + user { + id + } + text + done + } +} + +query findTodos { + todos { + text + done + user { + name + } + } +} +``` + +## Finishing touches + +At the top of our `resolver.go` add the following line: + +```go +//go:generate go run scripts/gqlgen.go -v +``` + +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: + +```go +go generate ./... +``` + +> Note +> +> Ensure that the path to your `gqlgen` binary is relative to the file the generate command is added to. diff --git a/docs/content/getting-started.md b/docs/content/getting-started.md index 9ea218afc26..37d0913e13d 100644 --- a/docs/content/getting-started.md +++ b/docs/content/getting-started.md @@ -3,7 +3,7 @@ linkTitle: Getting Started title: Building GraphQL servers in golang description: Get started building type-safe GraphQL servers in Golang using gqlgen menu: main -weight: -5 +weight: -7 --- This tutorial will take you through the process of building a GraphQL server with gqlgen that can: @@ -14,38 +14,18 @@ This tutorial will take you through the process of building a GraphQL server wit You can find the finished code for this tutorial [here](https://github.com/vektah/gqlgen-tutorials/tree/master/gettingstarted) -## Install gqlgen - -This article uses [`dep`](https://github.com/golang/dep) to install gqlgen. [Follow the instructions for your environment](https://github.com/golang/dep) to install. - -Assuming you already have a working [Go environment](https://golang.org/doc/install), create a directory for the project in your `$GOPATH`: - -```sh -$ mkdir -p $GOPATH/src/github.com/[username]/gqlgen-todos -``` - -> Go Modules +> Note > -> Currently `gqlgen` does not support Go Modules. This is due to the [`loader`](https://godoc.org/golang.org/x/tools/go/loader) package, that also does not yet support Go Modules. We are looking at solutions to this and the issue is tracked in Github. - -Add the following file to your project under `scripts/gqlgen.go`: - -```go -// +build ignore - -package main +> This tutorial uses Go Modules and requires Go 1.11+. If you want to use this tutorial without Go Modules, take a look at our [Getting Started Using dep]({{< ref "getting-started-dep.md" >}}) guide instead. -import "github.com/99designs/gqlgen/cmd" +## Setup Project -func main() { - cmd.Execute() -} -``` - -Lastly, initialise dep. This will inspect any imports you have in your project, and pull down the latest tagged release. +Create a directory for your project, and initialise it as a Go Module: ```sh -$ dep init +$ mkdir gqlgen-todos +$ cd gqlgen-todos +$ go mod init github.com/[username]/gqlgen-todos ``` ## Building the server @@ -85,7 +65,7 @@ type Mutation { ### Create the project skeleton ```bash -$ go run scripts/gqlgen.go init +$ go run github.com/99designs/gqlgen init ``` This has created an empty skeleton with all files you need: @@ -95,19 +75,13 @@ This has created an empty skeleton with all files you need: - `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. - - Now run dep ensure, so that we can ensure that the newly generated code's dependencies are all present: - - ```sh - $ dep ensure - ``` ### Create the database models The generated model for Todo isn't right, it has a user embeded in it but we only want to fetch it if the user actually requested it. So instead lets make a new model in `todo.go`: ```go -package gettingstarted +package gqlgen_todos type Todo struct { ID string @@ -122,17 +96,13 @@ Next tell gqlgen to use this new struct by adding it to `gqlgen.yml`: ```yaml models: Todo: - model: github.com/[username]/gqlgen-todos/gettingstarted.Todo + model: github.com/[username]/gqlgen-todos.Todo ``` Regenerate by running: ```bash -$ go run scripts/gqlgen.go -v -Unable to bind Todo.user to github.com/[username]/gqlgen-todos/gettingstarted.Todo - no method named user - no field named user - Adding resolver method +$ go run github.com/99designs/gqlgen ``` > Note @@ -141,20 +111,16 @@ Unable to bind Todo.user to github.com/[username]/gqlgen-todos/gettingstarted.To ### Implement the resolvers -The generated runtime has defined an interface for all the missing resolvers that we need to provide. Lets take a look in `generated.go` +The generated runtime has defined an interface for all the missing resolvers that we need to provide. Lets take a look in `generated.go`: ```go -// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. -func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { - return &executableSchema{ - resolvers: cfg.Resolvers, - directives: cfg.Directives, - } +func NewExecutableSchema(cfg Config) graphql.ExecutableSchema {} + // ... } type Config struct { Resolvers ResolverRoot - Directives DirectiveRoot + // ... } type ResolverRoot interface { @@ -163,23 +129,21 @@ type ResolverRoot interface { Todo() TodoResolver } -type DirectiveRoot struct { -} type MutationResolver interface { - CreateTodo(ctx context.Context, input NewTodo) (Todo, error) + CreateTodo(ctx context.Context, input NewTodo) (*Todo, error) } type QueryResolver interface { Todos(ctx context.Context) ([]Todo, error) } type TodoResolver interface { - User(ctx context.Context, obj *Todo) (User, error) + User(ctx context.Context, obj *Todo) (*User, error) } ``` Notice the `TodoResolver.User` method? Thats gqlgen saying "I dont know how to get a User from a Todo, you tell me.". Its worked out how to build everything else for us. -For any missing models (like NewTodo) gqlgen will generate a go struct. This is usually only used for input types and +For any missing models (like `NewTodo`) gqlgen will generate a go struct. This is usually only used for input types and one-off return values. Most of the time your types will be coming from the database, or an API client so binding is better than generating. @@ -189,15 +153,13 @@ This is a work in progress, we have a way to generate resolver stubs, but it can ```bash $ rm resolver.go -$ go run scripts/gqlgen.go +$ go run github.com/99designs/gqlgen ``` Now we just need to fill in the `not implemented` parts. Update `resolver.go` ```go -//go:generate go run ./scripts/gqlgen.go - -package gettingstarted +package gqlgen_todos import ( context "context" @@ -205,7 +167,7 @@ import ( "math/rand" ) -type Resolver struct{ +type Resolver struct { todos []Todo } @@ -221,13 +183,13 @@ func (r *Resolver) Todo() TodoResolver { type mutationResolver struct{ *Resolver } -func (r *mutationResolver) CreateTodo(ctx context.Context, input NewTodo) (Todo, error) { - todo := Todo{ +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) + r.todos = append(r.todos, *todo) return todo, nil } @@ -239,8 +201,8 @@ func (r *queryResolver) Todos(ctx context.Context) ([]Todo, error) { 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 +func (r *todoResolver) User(ctx context.Context, obj *Todo) (*User, error) { + return &User{ID: obj.UserID, Name: "user " + obj.UserID}, nil } ``` @@ -278,7 +240,7 @@ query findTodos { At the top of our `resolver.go` add the following line: ```go -//go:generate go run scripts/gqlgen.go -v +//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. To run go generate recursively over your entire project, use this command: @@ -286,7 +248,3 @@ This magic comment tells `go generate` what command to run when we want to regen ```go go generate ./... ``` - -> Note -> -> Ensure that the path to your `gqlgen` binary is relative to the file the generate command is added to. diff --git a/docs/layouts/_default/baseof.html b/docs/layouts/_default/baseof.html index 4fb29e68095..70eea20aba6 100644 --- a/docs/layouts/_default/baseof.html +++ b/docs/layouts/_default/baseof.html @@ -9,7 +9,7 @@ {{ if not .IsHome }}{{ .Title }} —{{ end }} {{ .Site.Title }} - + diff --git a/docs/static/main.css b/docs/static/main.css index 2178cbac662..563955595f5 100644 --- a/docs/static/main.css +++ b/docs/static/main.css @@ -221,10 +221,6 @@ ul.submenu span { padding: 5px 10px; } -ul.menu li { - font-weight: 400; -} - ul.menu li.active, ul.menu a:hover { background-color: var(--color-nav-active); @@ -389,7 +385,6 @@ em { color: var(--color-heading-text); background-color: var(--color-heading-background); font-family: var(--font-heading); - font-weight: 500; list-style-type: none; -webkit-font-smoothing: antialiased; diff --git a/internal/code/imports_test.go b/internal/code/imports_test.go index 2612ffce5fb..b1825749259 100644 --- a/internal/code/imports_test.go +++ b/internal/code/imports_test.go @@ -14,7 +14,7 @@ func TestImportPathForDir(t *testing.T) { require.NoError(t, err) assert.Equal(t, "github.com/99designs/gqlgen/internal/code", ImportPathForDir(wd)) - assert.Equal(t, "github.com/99designs/gqlgen", ImportPathForDir(filepath.Join(wd, "..", ".."))) + assert.Equal(t, "github.com/99designs/gqlgen/api", ImportPathForDir(filepath.Join(wd, "..", "..", "api"))) // doesnt contain go code, but should still give a valid import path assert.Equal(t, "github.com/99designs/gqlgen/docs", ImportPathForDir(filepath.Join(wd, "..", "..", "docs"))) @@ -24,7 +24,7 @@ func TestImportPathForDir(t *testing.T) { } func TestNameForPackage(t *testing.T) { - assert.Equal(t, "gqlgen", NameForPackage("github.com/99designs/gqlgen")) + assert.Equal(t, "api", NameForPackage("github.com/99designs/gqlgen/api")) // does not contain go code, should still give a valid name assert.Equal(t, "docs", NameForPackage("github.com/99designs/gqlgen/docs")) diff --git a/main.go b/main.go new file mode 100644 index 00000000000..dbc24135388 --- /dev/null +++ b/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/99designs/gqlgen/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/testdata/gqlgen.go b/testdata/gqlgen.go index d69313e9e8e..f4bbe75507d 100644 --- a/testdata/gqlgen.go +++ b/testdata/gqlgen.go @@ -8,7 +8,7 @@ import ( "os" "time" - "github.com/99designs/gqlgen" + "github.com/99designs/gqlgen/api" "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/plugin/stubgen" ) @@ -27,12 +27,12 @@ func main() { os.Exit(2) } - var options []gqlgen.Option + var options []api.Option if *stub != "" { - options = append(options, gqlgen.AddPlugin(stubgen.New(*stub, "Stub"))) + options = append(options, api.AddPlugin(stubgen.New(*stub, "Stub"))) } - err = gqlgen.Generate(cfg, options...) + err = api.Generate(cfg, options...) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(3)