昨天發完文之後,一直覺得怪怪的,於是今天就去翻了 graphql.go 還有 gqlgen 產生出來的 exec.go,發現有一些地方要修改,抱歉啦。
$ vim internal/gql/schema/schema.graphql
scalar Time
# Types
type User {
  id: ID!
  email: String!
  userId: String
  name: String
  firstName: String
  lastName: String
  nickName: String
  description: String
  location: String
  createdAt: Time!
  updatedAt: Time
  posts: [Post!]!
}
type Post {
  id: ID!
  title: String!
  content: String
  createdAt: Time!
  updatedAt: Time
  user: User
}
# Input Types
input UserInput {
  email: String
  userId: String
  displayName: String
  name: String
  firstName: String
  lastName: String
  nickName: String
  description: String
  location: String
}
input PostInput {
  title: String
  content: String
  userId: String
}
# Define mutations here
type Mutation {
  createUser(input: UserInput!): User!
  updateUser(id: ID!, input: UserInput!): User!
  deleteUser(id: ID!): Boolean!
  createPost(input: PostInput!): Post!
  updatePost(id: ID!, input: PostInput!): Post!
  deletePost(id: ID!): Boolean!
}
# Define queries here
type Query {
  users: [User!]!
  posts: [Post!]!
  user(id: ID!): User
  post(id: ID!): Post
}
修改這個是因為可以直接使用 orm 定義好的 models 來喂給 gelgen,這樣就不需要再將 orm 的 model 轉換成 gqlgen 的 model,而且生成的 exec.go 以及 resolvers.go 跟著改變,會幫我們將所有需要實作的 resolvers 都列出來,昨天的並沒有幫我們列出 User & Post 間需要的 resolvers。
$ vim gqlgem.yml
schema:
  - internal/gql/schemas/schema.graphql
# Let gqlgen know where to put the generated server
exec:
  filename: internal/gql/generated/exec.go # 這邊路徑有改
  package: generated
# Let gqlgen know where to put the generated models (if any)
model:
  filename: internal/gql/models/generated.go
  package: gqlmodels # 這邊 package name 有改
# Let gqlgen know where to put the generated resolvers
resolver:
  filename: internal/gql/resolvers/generated/generated.go
  type: Resolver
  package: resolvers
autobind: []
# 加上 models,並且指定到 orm 的 models
models:
  User:
    model: github.com/wtlin1228/go-gql-server/internal/orm/models.User
  Post:
    model: github.com/wtlin1228/go-gql-server/internal/orm/models.Post
修改後執行 $ scripts/gqlgen.sh
執行完 gqlgen 之後會發現自動產生的三個檔案都有變化
一樣將 gqlgen 產生的 resolvers 檔案拆成三塊
$ vim internal/gql/resolvers/main.go
這裡跟昨天一樣幾乎沒變
 package resolvers
 import (
 	"github.com/wtlin1228/go-gql-server/internal/gql/generated"
 	"github.com/wtlin1228/go-gql-server/internal/orm"
 )
 type Resolver struct {
 	ORM *orm.ORM
 }
 func (r *Resolver) Mutation() generated.MutationResolver {
 	return &mutationResolver{r}
 }
 func (r *Resolver) Post() generated.PostResolver {
 	return &postResolver{r}
 }
 func (r *Resolver) Query() generated.QueryResolver {
 	return &queryResolver{r}
 }
 func (r *Resolver) User() generated.UserResolver {
 	return &userResolver{r}
 }
 type mutationResolver struct{ *Resolver }
 type queryResolver struct{ *Resolver }
$ vim internal/gql/resolvers/post.go
寫法跟昨天很像,只是精簡了很多,然後多了一些 resolvers
 package resolvers
 import (
 	"context"
 	"github.com/gofrs/uuid"
 	gqlmodels "github.com/wtlin1228/go-gql-server/internal/gql/models"
 	"github.com/wtlin1228/go-gql-server/internal/orm/models"
 )
 // Mutations
 func (r *mutationResolver) CreatePost(ctx context.Context, input gqlmodels.PostInput) (*models.Post, error) {
 	return postCreateUpdate(r, input, false)
 }
 func (r *mutationResolver) UpdatePost(ctx context.Context, id string, input gqlmodels.PostInput) (*models.Post, error) {
 	return postCreateUpdate(r, input, true, id)
 }
 func (r *mutationResolver) DeletePost(ctx context.Context, id string) (bool, error) {
 	return postDelete(r, id)
 }
 // Queries
 func (r *queryResolver) Posts(ctx context.Context) ([]*models.Post, error) {
 	var posts []*models.Post
 	r.ORM.DB.Find(&posts)
 	return posts, nil
 }
 func (r *queryResolver) Post(ctx context.Context, id string) (*models.Post, error) {
 	post := &models.Post{}
 	r.ORM.DB.First(&post)
 	return post, nil
 }
 type postResolver struct{ *Resolver }
 func (r *postResolver) ID(ctx context.Context, obj *models.Post) (string, error) {
 	return obj.ID.String(), nil
 }
 func (r *postResolver) User(ctx context.Context, obj *models.Post) (*models.User, error) {
 	return r.Query().User(ctx, obj.UserID.String())
 }
 // Mutation Helper functions
 func postCreateUpdate(r *mutationResolver, input gqlmodels.PostInput, update bool, ids ...string) (*models.Post, error) {
 	dbo, err := GQLInputPostToDBPost(&input, update, ids...)
 	if err != nil {
 		return nil, err
 	}
 	// Create scoped clean db interface
 	db := r.ORM.DB.New().Begin()
 	if !update {
 		db = db.Create(dbo).First(dbo) // Create the post
 	} else {
 		db = db.Model(&dbo).Update(dbo).First(dbo) // Or update it
 	}
 	if db.Error != nil {
 		db.RollbackUnlessCommitted()
 		return nil, db.Error
 	}
 	db = db.Commit()
 	return dbo, nil
 }
 func postDelete(r *mutationResolver, id string) (bool, error) {
 	whereID := "id = ?"
 	// Convert id from type string to type uuid.UUID
 	convertedID, err := uuid.FromString(id)
 	if err != nil {
 		return false, err
 	}
 	// Create scoped clean db interface
 	db := r.ORM.DB.New().Begin()
 	// Find the post
 	dbPost := &models.Post{}
 	err = db.Where(whereID, convertedID).First(dbPost).Error
 	if err != nil {
 		return false, err
 	}
 	// Delete the post
 	if err := db.Delete(dbPost).Error; err != nil {
 		db.RollbackUnlessCommitted()
 		return false, err
 	}
 	db = db.Commit()
 	return true, nil
 }
 // GQLInputPostToDBPost transforms [post] gql input to db model
 func GQLInputPostToDBPost(i *gqlmodels.PostInput, update bool, ids ...string) (o *models.Post, err error) {
 	o = &models.Post{
 		Title:   *i.Title,
 		Content: i.Content,
 	}
 	// convert the id from type String to type uuid.UUID
 	if len(ids) > 0 {
 		updID, err := uuid.FromString(ids[0])
 		if err != nil {
 			return nil, err
 		}
 		o.ID = updID
 	}
 	return o, err
 }
$ vim internal/gql/resolvers/user.go
寫法跟昨天很像,只是精簡了很多,然後多了一些 resolvers
 package resolvers
 import (
 	"context"
 	"errors"
 	"github.com/gofrs/uuid"
 	gqlmodels "github.com/wtlin1228/go-gql-server/internal/gql/models"
 	"github.com/wtlin1228/go-gql-server/internal/orm/models"
 )
 // Mutations
 func (r *mutationResolver) CreateUser(ctx context.Context, input gqlmodels.UserInput) (*models.User, error) {
 	return userCreateUpdate(r, input, false)
 }
 func (r *mutationResolver) UpdateUser(ctx context.Context, id string, input gqlmodels.UserInput) (*models.User, error) {
 	return userCreateUpdate(r, input, true, id)
 }
 func (r *mutationResolver) DeleteUser(ctx context.Context, id string) (bool, error) {
 	return userDelete(r, id)
 }
 // Queries
 func (r *queryResolver) Users(ctx context.Context) ([]*models.User, error) {
 	var users []*models.User
 	r.ORM.DB.Preload("Posts").Find(&users)
 	return users, nil
 }
 func (r *queryResolver) User(ctx context.Context, id string) (*models.User, error) {
 	user := &models.User{}
 	r.ORM.DB.Preload("Posts").First(&user)
 	return user, nil
 }
 type userResolver struct{ *Resolver }
 func (r *userResolver) ID(ctx context.Context, obj *models.User) (string, error) {
 	return obj.ID.String(), nil
 }
 // Mutation Helper functions
 func userCreateUpdate(r *mutationResolver, input gqlmodels.UserInput, update bool, ids ...string) (*models.User, error) {
 	dbo, err := GQLInputUserToDBUser(&input, update, ids...)
 	if err != nil {
 		return nil, err
 	}
 	// Create scoped clean db interface
 	db := r.ORM.DB.New().Begin()
 	if !update {
 		db = db.Create(dbo).First(dbo) // Create the user
 	} else {
 		db = db.Model(&dbo).Update(dbo).First(dbo) // Or update it
 	}
 	if db.Error != nil {
 		db.RollbackUnlessCommitted()
 		return nil, db.Error
 	}
 	db = db.Commit()
 	return dbo, nil
 }
 func userDelete(r *mutationResolver, id string) (bool, error) {
 	whereID := "id = ?"
 	// Convert id to uuid.UUID from string
 	convertedID, err := uuid.FromString(id)
 	if err != nil {
 		return false, err
 	}
 	// Create scoped clean db interface
 	db := r.ORM.DB.New().Begin()
 	// Find the user
 	dbUser := &models.User{}
 	err = db.Where(whereID, convertedID).First(dbUser).Error
 	if err != nil {
 		return false, err
 	}
 	// Find the user's posts
 	dbPosts := []*models.Post{}
 	db.Model(&dbUser).Related(&dbPosts, "Posts")
 	// Delete posts
 	for _, dbPost := range dbPosts {
 		if err := db.Delete(dbPost).Error; err != nil {
 			db.RollbackUnlessCommitted()
 			return false, err
 		}
 	}
 	// Delete the user
 	if err := db.Delete(dbUser).Error; err != nil {
 		db.RollbackUnlessCommitted()
 		return false, err
 	}
 	db = db.Commit()
 	return true, nil
 }
 // GQLInputUserToDBUser transforms [user] gql input to db model
 func GQLInputUserToDBUser(i *gqlmodels.UserInput, update bool, ids ...string) (o *models.User, err error) {
 	o = &models.User{
 		UserID:      i.UserID,
 		Name:        i.Name,
 		FirstName:   i.FirstName,
 		LastName:    i.LastName,
 		NickName:    i.NickName,
 		Description: i.Description,
 		Location:    i.Location,
 	}
 	if i.Email == nil && !update {
 		return nil, errors.New("Field [email] is required")
 	}
 	if i.Email != nil {
 		o.Email = *i.Email
 	}
 	if len(ids) > 0 {
 		updID, err := uuid.FromString(ids[0])
 		if err != nil {
 			return nil, err
 		}
 		o.ID = updID
 	}
 	return o, err
 }
package handlers
import (
	"github.com/99designs/gqlgen/handler"
	"github.com/gin-gonic/gin"
  // 改這邊
	gql "github.com/wtlin1228/go-gql-server/internal/gql/generated"
	"github.com/wtlin1228/go-gql-server/internal/gql/resolvers"
	"github.com/wtlin1228/go-gql-server/internal/orm"
)
// GraphqlHandler defines the GQLGen GraphQL server handler
func GraphqlHandler(orm *orm.ORM) gin.HandlerFunc {
	// NewExecutableSchema and Config are in the generated.go file
	c := gql.Config{
		Resolvers: &resolvers.Resolver{
			ORM: orm, // pass in the ORM instance in the resolvers to be used
		},
	}
	h := handler.GraphQL(gql.NewExecutableSchema(c))
	return func(c *gin.Context) {
		h.ServeHTTP(c.Writer, c.Request)
	}
}
// PlaygroundHandler Defines the Playground handler to expose our playground
func PlaygroundHandler(path string) gin.HandlerFunc {
	h := handler.Playground("Go GraphQL Server", path)
	return func(c *gin.Context) {
		h.ServeHTTP(c.Writer, c.Request)
	}
}
$ scripts/run.sh
測試一下,沒有問題!
程式碼放在這邊 Here
https://gqlgen.com/getting-started/
https://stackedco.de/building-a-recipe-crud-api-with-golang-graphql-and-mysql
https://blog.csdn.net/liuyh73/article/details/85028977