昨天發完文之後,一直覺得怪怪的,於是今天就去翻了 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