【GraphQL&Go】gqlgen basics and MySQL

Basic configuration of graphql

To understand the basic structure of graphql, let’s take a look at the following flow. As the file grows larger and larger, it becomes difficult to imagine the whole picture, so let’s look at the contents from the very first simple structure.

1–1. Make a skeleton project

Following Getting Started, install gqlgen in Go’s project and create a skeleton project (template).

# create go module
$ mkdir gqlgen-todos
$ cd gqlgen-todos
$ go mod init github.com/[username]/gqlgen-todos
# create skeleton project of gqlgen
$ go get github.com/99designs/gqlgen
$ go run github.com/99designs/gqlgen init
├── go.mod
├── go.sum
├── gqlgen.yml // Code generation configuration file
├── graph
│ ├── generated // Automatically generated package
│ │ └── generated.go
│ ├── model // Package for graph model implemented in Go
│ │ └── models_gen.go
│ ├── resolver.go // Root resolver type definition file.
│ ├── schema.graphqls // GraphQL schema definition file.
│ └── schema.resolvers.go // resolver implementation file
└── server.go // Entry point to the app. Not overwritten by regeneration.
  • gqlgen.yml: configuration file
  • graph: A package that contains various files used in the development of gqlgen (files here will be described later).
  • server.go: main function

1–2. About each role of the generated file

graph/schema.graphqls: GraphQL schema definition file
graph/schema.graphqls is a GraphQL schema definition file whose role is to manage API endpoints and their types. In the skeleton project, it is generated as follows. After that, I will add ⭐️ to the parts I added.

# ⭐️ type: Basic object type
type Todo {
id: ID!
text: String!
done: Boolean!
user: User!
}
type User {
id: ID!
name: String!
}
# ⭐️ type Query: Data fetch endpoint definition
type Query {
todos: [Todo!]!
}
# ⭐️ input: Definition of object type for Mutaion
input NewTodo {
text: String!
userId: String!
}
# ⭐️ type Mutation: Endpoint definition of the process to modify the data of Serverside
type Mutation {
createTodo (input: NewTodo!): Todo!
}
$ go run github.com/99designs/gqlgen generate

graph/model/models_gen.go: Go type definition file automatically generated from graph/schema.graphqls

graph/model/models_gen.go is a Go type definition file automatically generated from graph/schema.graphqls. As you can see by comparing with graph/schema.graphqls above, type Todo, type User and input NewTodo structs other than type Query and type Mutation are automatically generated.

// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.package model// ⭐️ input File generated from New Todo
type NewTodo struct {
Text string `json:" text "`
UserID string `json:"userId "`
}
// File generated from ⭐️ type Todo
type Todo struct {
ID string `json:" id "`
Text string `json:" text "`
Done bool `json:" done "`
User * User `json:" user "`
}
// File generated from ⭐️ type User
type User struct {
ID string `json:" id "`
Name string `json:" name "` `
Friends [] * User `json:" friends "`
}

graph/schema.resolvers.go: Go file for endpoint implementation automatically generated from graph/schema.graphqls file

graph/schema.resolvers.go is an automatically generated Go file for endpoint implementation from the graph/schema.graphqls file. Endpoint management is your role. In the first state, the code is generated in the unimplemented state as shown below, so the flow is to implement it there.

package graph// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
import (
"context"
"fmt"
"math / rand"
"github.com/shkomine/gqlgen-todos/graph/generated"
"github.com/shkomine/gqlgen-todos/graph/model"
)
func (r * mutationResolver) CreateTodo (ctx context.Context, input model.NewTodo) (* model.Todo, error) {
// ⭐️ Implement data fetch here
// For example, this process
// r.todoRepo.Create (input)
panic (fmt.Errorf ("not implemented"))
}
func (r * queryResolver) Todos (ctx context.Context) ([] * model.Todo, error) {
// ⭐️ Implement data changes here
// For example, this process
// r.todoRepo.List ()
panic (fmt.Errorf ("not implemented"))
}
// Mutation returns generated. MutationResolver implementation.
func (r * Resolver) Mutation () generated.MutationResolver {return & mutationResolver {r}}
// Query returns generated.QueryResolver implementation.
func (r * Resolver) Query () generated.QueryResolver {return & queryResolver {r}}
type mutationResolver struct {* Resolver}
type queryResolver struct {* Resolver}
type mutationResolver struct {* Resolver}
type queryResolver struct {* Resolver}

graph/resolver.go, server.go: go package not regenerated by gqlgen command

The type definition of Resolver is defined in graph/resolver.go, and its role is to centrally manage the state. The type cannot be renamed or deleted as it is referenced from the auto-generated graph/schema.resolvers.go, but this Resolver will not be regenerated by running the gqlgen command after initial generation. Since there is no such thing, this struct can have an instance (pointer) etc. that you want to keep referencing. If it is MVC, I think that it will be an implementation that has structs for each model, other repository, service, and use case.

package graphimport "github.com/shkomine/gqlgen-todos/graph/model"// This file will not be regenerated automatically.
// //
// It serves as dependency injection for your app, add any dependencies you require here.
type Resolver struct {
// ⭐️ TODO: Hold an instance such as UserRepository here
}
package mainimport (
"log"
"net / http"
"os"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/shkomine/gqlgen-todos/graph"
"github.com/shkomine/gqlgen-todos/graph/generated"
)
const defaultPort = "8080"func main () {
port: = os.Getenv ("PORT")
if port == "" {
port = defaultPort
}
// ⭐️ Create an instance of Resolver here
srv: = handler.NewDefaultServer (generated.NewExecutableSchema (generated.Config {Resolvers: & graph.Resolver {}}))
// ⭐️ set graphql playground to `/ query`
http.Handle ("/", playground.Handler ("GraphQL playground", "/ query"))
http.Handle ("/ query", srv)
log.Printf ("connect to http: // localhost:% s / for GraphQL playground", port)
log.Fatal (http.ListenAndServe (":" + port, nil))
}

Let’s connect GraphQL to MySQL

I have already prepared Database and users table with columns which contain id, username, email, password, created_at, is_deleted.

# GraphQL schema example
# https://gqlgen.com/getting-started/
scalar Timetype User {
id: ID!
username: String!
email: String!
password: String!
created_at: Time!
is_deleted: Boolean!
}
type Query {
users: [User!]!
}
input NewUser {
username: String!
email: String!
password: String!
}
type Mutation {
createUser(input: NewUser!): User!
}
$ go run github.com/99designs/gqlgen generate
$ go get github.com/go-sql-driver/mysql
package dbimport (
"database/sql"
"fmt"
"os"
_ "github.com/go-sql-driver/mysql"
)
func ConnectDB() (*sql.DB, error) {
USERNAME := os.Getenv("USERNAME")
PASSWORD := os.Getenv("PASSWORD")
HOST := os.Getenv("HOST")
PORT := os.Getenv("PORT")
DATABASE := os.Getenv("DATABASE")
dbconf := USERNAME + ":" + PASSWORD + "@tcp(" + HOST + ":" + PORT + ")/" + DATABASE + "?charset=utf8mb4" + "&parseTime=True" db, err := sql.Open(os.Getenv("DRIVER"), dbconf)
if err != nil {
fmt.Println("Error connecting to database : error=%v", err)
return nil, err
}
return db, err
}
DRIVER=mysql
DSN=user:password@tcp(db-dev:3306)/db_dev
HOST=YOUR_HOST
DATABASE=YOUR_DATABASE
USERNAME=YOUR_NAME
PASSWORD=YOUR_PASSWORD
PORT=YOUR_PORT
$ go get github.com/joho/godotenv
package mainimport (
"log"
"net/http"
"os"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/TIshow/learn-to-donate/graph"
"github.com/TIshow/learn-to-donate/graph/generated"
"github.com/joho/godotenv"
)
func envLoad() {
err := godotenv.Load(".env")
if err != nil {
log.Fatalf("Error loading env target")
}
}
const defaultPort = "3306"func main() {
envLoad() // Load from .env file
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}})) http.Handle("/", playground.Handler("GraphQL playground", "/query"))
http.Handle("/query", srv)
log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
package graph// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
import (
"context"
"fmt"
"time"
"github.com/TIshow/learn-to-donate/db"
"github.com/TIshow/learn-to-donate/graph/generated"
"github.com/TIshow/learn-to-donate/graph/model"
)
func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {
db, err := db.ConnectDB() // connect to DB.
if err != nil {
panic(err)
} else {
fmt.Println("Successful Connection to DB !")
}
var user model.User
user.Username = input.Username
user.Email = input.Email
user.Password = input.Password
user.IsDeleted = false
now := time.Now()
user.CreatedAt = now
_, err = db.Exec(`INSERT INTO users (username, email, password, created_at, updated_at, is_deleted) VALUES (?, ?, ?, ?, ?, ?)`, user.Username, user.Email, user.Password, user.CreatedAt, user.UpdatedAt, user.IsDeleted)
if err != nil {
panic(err)
} else {
fmt.Println("Insert User is successed !")
}
defer db.Close() return &user, nil
}
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
return r.users, nil
}
// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
db, err := db.ConnectDB() // connect to DB.
var user model.User
user.Username = input.Username
user.Email = input.Email
user.Password = input.Password
user.IsDeleted = false
_, err = db.Exec(`INSERT INTO users (username, email, password, created_at, updated_at, is_deleted) VALUES (?, ?, ?, ?, ?, ?)`, user.Username, user.Email, user.Password, user.CreatedAt, user.UpdatedAt, user.IsDeleted)
mutation {
createUser(input: {username: "test", email: "test", password: "test"}){
id
username
email
password
created_at
is_deleted
}
}

Conclusion

I was confused about what to write in which file and what to pass as an argument, but I’m glad I managed to do it.
The one that actually works is different from the name of the structure, so please let me know if it doesn’t work in the above example.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
TiShow

TiShow

80% is the creation, the rest is depression. Frontend developer and data scientist, designer. Looking for Physics Ph.D Twitter: @_t_i_show