3.6.3 GraphQL 服务端实现 #
在理解了 GraphQL 的基础概念和 Schema 设计后,本节将详细介绍如何使用 Go 语言构建完整的 GraphQL 服务器。我们将使用流行的 GraphQL Go 库来实现查询、变更、订阅等功能。
GraphQL Go 库选择 #
Go 生态系统中有几个优秀的 GraphQL 库:
- graphql-go/graphql - 功能完整的 GraphQL 实现
- 99designs/gqlgen - 代码生成优先的 GraphQL 库
- graph-gophers/graphql-go - 轻量级的 GraphQL 实现
本节主要使用 graphql-go/graphql
库进行演示。
项目初始化 #
首先创建项目结构并安装依赖:
mkdir graphql-server
cd graphql-server
go mod init graphql-server
# 安装依赖
go get github.com/graphql-go/graphql
go get github.com/graphql-go/handler
go get github.com/gin-gonic/gin
go get gorm.io/gorm
go get gorm.io/driver/sqlite
项目结构:
graphql-server/
├── main.go
├── models/
│ ├── user.go
│ ├── post.go
│ └── comment.go
├── schema/
│ ├── schema.go
│ ├── types.go
│ ├── queries.go
│ ├── mutations.go
│ └── subscriptions.go
├── resolvers/
│ ├── user.go
│ ├── post.go
│ └── comment.go
├── database/
│ └── database.go
└── utils/
└── auth.go
数据模型定义 #
首先定义数据模型:
// models/user.go
package models
import (
"time"
"gorm.io/gorm"
)
type User struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"not null"`
Email string `json:"email" gorm:"uniqueIndex;not null"`
Avatar string `json:"avatar"`
Role string `json:"role" gorm:"default:user"`
IsActive bool `json:"isActive" gorm:"default:true"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
// 关联
Posts []Post `json:"posts" gorm:"foreignKey:AuthorID"`
Comments []Comment `json:"comments" gorm:"foreignKey:AuthorID"`
Profile *UserProfile `json:"profile" gorm:"foreignKey:UserID"`
}
type UserProfile struct {
ID uint `json:"id" gorm:"primaryKey"`
UserID uint `json:"userId" gorm:"uniqueIndex"`
Bio string `json:"bio"`
Website string `json:"website"`
Location string `json:"location"`
User User `json:"user" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
// models/post.go
package models
import (
"time"
"gorm.io/gorm"
)
type Post struct {
ID uint `json:"id" gorm:"primaryKey"`
Title string `json:"title" gorm:"not null"`
Content string `json:"content" gorm:"type:text"`
Status string `json:"status" gorm:"default:draft"`
AuthorID uint `json:"authorId" gorm:"not null"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
// 关联
Author User `json:"author" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
Comments []Comment `json:"comments" gorm:"foreignKey:PostID"`
Tags []Tag `json:"tags" gorm:"many2many:post_tags;"`
}
type Tag struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"uniqueIndex;not null"`
Posts []Post `json:"posts" gorm:"many2many:post_tags;"`
}
// models/comment.go
package models
import (
"time"
"gorm.io/gorm"
)
type Comment struct {
ID uint `json:"id" gorm:"primaryKey"`
Content string `json:"content" gorm:"not null"`
PostID uint `json:"postId" gorm:"not null"`
AuthorID uint `json:"authorId" gorm:"not null"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
// 关联
Post Post `json:"post" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
Author User `json:"author" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
数据库初始化 #
// database/database.go
package database
import (
"log"
"graphql-server/models"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var DB *gorm.DB
func InitDatabase() {
var err error
DB, err = gorm.Open(sqlite.Open("test.db"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
// 自动迁移
err = DB.AutoMigrate(
&models.User{},
&models.UserProfile{},
&models.Post{},
&models.Comment{},
&models.Tag{},
)
if err != nil {
log.Fatal("Failed to migrate database:", err)
}
// 创建测试数据
seedData()
}
func seedData() {
// 创建用户
users := []models.User{
{
Name: "John Doe",
Email: "[email protected]",
Avatar: "https://example.com/john.jpg",
Role: "admin",
IsActive: true,
},
{
Name: "Jane Smith",
Email: "[email protected]",
Avatar: "https://example.com/jane.jpg",
Role: "user",
IsActive: true,
},
}
for _, user := range users {
var existingUser models.User
if err := DB.Where("email = ?", user.Email).First(&existingUser).Error; err != nil {
DB.Create(&user)
}
}
// 创建文章
var john, jane models.User
DB.Where("email = ?", "[email protected]").First(&john)
DB.Where("email = ?", "[email protected]").First(&jane)
posts := []models.Post{
{
Title: "GraphQL Introduction",
Content: "GraphQL is a query language for APIs...",
Status: "published",
AuthorID: john.ID,
},
{
Title: "Go Programming Tips",
Content: "Here are some useful Go programming tips...",
Status: "published",
AuthorID: jane.ID,
},
}
for _, post := range posts {
var existingPost models.Post
if err := DB.Where("title = ?", post.Title).First(&existingPost).Error; err != nil {
DB.Create(&post)
}
}
}
GraphQL 类型定义 #
// schema/types.go
package schema
import (
"time"
"github.com/graphql-go/graphql"
"github.com/graphql-go/graphql/language/ast"
)
// 自定义标量类型
var DateType = graphql.NewScalar(graphql.ScalarConfig{
Name: "Date",
Description: "Date custom scalar type",
Serialize: func(value interface{}) interface{} {
switch v := value.(type) {
case time.Time:
return v.Format(time.RFC3339)
case *time.Time:
return v.Format(time.RFC3339)
default:
return nil
}
},
ParseValue: func(value interface{}) interface{} {
switch v := value.(type) {
case string:
t, err := time.Parse(time.RFC3339, v)
if err != nil {
return nil
}
return t
default:
return nil
}
},
ParseLiteral: func(valueAST ast.Value) interface{} {
switch valueAST := valueAST.(type) {
case *ast.StringValue:
t, err := time.Parse(time.RFC3339, valueAST.Value)
if err != nil {
return nil
}
return t
default:
return nil
}
},
})
// 枚举类型
var UserRoleEnum = graphql.NewEnum(graphql.EnumConfig{
Name: "UserRole",
Description: "User role enumeration",
Values: graphql.EnumValueConfigMap{
"ADMIN": &graphql.EnumValueConfig{
Value: "admin",
Description: "Administrator role",
},
"USER": &graphql.EnumValueConfig{
Value: "user",
Description: "Regular user role",
},
},
})
var PostStatusEnum = graphql.NewEnum(graphql.EnumConfig{
Name: "PostStatus",
Description: "Post status enumeration",
Values: graphql.EnumValueConfigMap{
"DRAFT": &graphql.EnumValueConfig{
Value: "draft",
Description: "Draft post",
},
"PUBLISHED": &graphql.EnumValueConfig{
Value: "published",
Description: "Published post",
},
},
})
// 对象类型声明(避免循环依赖)
var UserType *graphql.Object
var UserProfileType *graphql.Object
var PostType *graphql.Object
var CommentType *graphql.Object
var TagType *graphql.Object
// 输入类型
var CreateUserInputType = graphql.NewInputObject(graphql.InputObjectConfig{
Name: "CreateUserInput",
Description: "Input for creating a user",
Fields: graphql.InputObjectConfigFieldMap{
"name": &graphql.InputObjectFieldConfig{
Type: graphql.NewNonNull(graphql.String),
Description: "User name",
},
"email": &graphql.InputObjectFieldConfig{
Type: graphql.NewNonNull(graphql.String),
Description: "User email",
},
"avatar": &graphql.InputObjectFieldConfig{
Type: graphql.String,
Description: "User avatar URL",
},
"role": &graphql.InputObjectFieldConfig{
Type: UserRoleEnum,
DefaultValue: "user",
Description: "User role",
},
},
})
var UpdateUserInputType = graphql.NewInputObject(graphql.InputObjectConfig{
Name: "UpdateUserInput",
Description: "Input for updating a user",
Fields: graphql.InputObjectConfigFieldMap{
"name": &graphql.InputObjectFieldConfig{
Type: graphql.String,
Description: "User name",
},
"email": &graphql.InputObjectFieldConfig{
Type: graphql.String,
Description: "User email",
},
"avatar": &graphql.InputObjectFieldConfig{
Type: graphql.String,
Description: "User avatar URL",
},
"role": &graphql.InputObjectFieldConfig{
Type: UserRoleEnum,
Description: "User role",
},
"isActive": &graphql.InputObjectFieldConfig{
Type: graphql.Boolean,
Description: "User active status",
},
},
})
var CreatePostInputType = graphql.NewInputObject(graphql.InputObjectConfig{
Name: "CreatePostInput",
Description: "Input for creating a post",
Fields: graphql.InputObjectConfigFieldMap{
"title": &graphql.InputObjectFieldConfig{
Type: graphql.NewNonNull(graphql.String),
Description: "Post title",
},
"content": &graphql.InputObjectFieldConfig{
Type: graphql.NewNonNull(graphql.String),
Description: "Post content",
},
"status": &graphql.InputObjectFieldConfig{
Type: PostStatusEnum,
DefaultValue: "draft",
Description: "Post status",
},
"tags": &graphql.InputObjectFieldConfig{
Type: graphql.NewList(graphql.String),
Description: "Post tags",
},
},
})
解析器实现 #
// resolvers/user.go
package resolvers
import (
"context"
"errors"
"strconv"
"graphql-server/database"
"graphql-server/models"
"github.com/graphql-go/graphql"
)
type UserResolver struct{}
func (r *UserResolver) GetUser(p graphql.ResolveParams) (interface{}, error) {
id, ok := p.Args["id"].(string)
if !ok {
return nil, errors.New("invalid user ID")
}
userID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, errors.New("invalid user ID format")
}
var user models.User
if err := database.DB.Preload("Profile").First(&user, uint(userID)).Error; err != nil {
return nil, err
}
return &user, nil
}
func (r *UserResolver) GetUsers(p graphql.ResolveParams) (interface{}, error) {
var users []models.User
query := database.DB.Preload("Profile")
// 处理过滤参数
if role, ok := p.Args["role"].(string); ok && role != "" {
query = query.Where("role = ?", role)
}
if search, ok := p.Args["search"].(string); ok && search != "" {
query = query.Where("name LIKE ? OR email LIKE ?", "%"+search+"%", "%"+search+"%")
}
if err := query.Find(&users).Error; err != nil {
return nil, err
}
return users, nil
}
func (r *UserResolver) CreateUser(p graphql.ResolveParams) (interface{}, error) {
input, ok := p.Args["input"].(map[string]interface{})
if !ok {
return nil, errors.New("invalid input")
}
user := models.User{
Name: input["name"].(string),
Email: input["email"].(string),
Role: input["role"].(string),
IsActive: true,
}
if avatar, ok := input["avatar"].(string); ok {
user.Avatar = avatar
}
if err := database.DB.Create(&user).Error; err != nil {
return nil, err
}
return &user, nil
}
func (r *UserResolver) UpdateUser(p graphql.ResolveParams) (interface{}, error) {
id, ok := p.Args["id"].(string)
if !ok {
return nil, errors.New("invalid user ID")
}
userID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, errors.New("invalid user ID format")
}
input, ok := p.Args["input"].(map[string]interface{})
if !ok {
return nil, errors.New("invalid input")
}
var user models.User
if err := database.DB.First(&user, uint(userID)).Error; err != nil {
return nil, err
}
// 更新字段
updates := make(map[string]interface{})
if name, ok := input["name"].(string); ok {
updates["name"] = name
}
if email, ok := input["email"].(string); ok {
updates["email"] = email
}
if avatar, ok := input["avatar"].(string); ok {
updates["avatar"] = avatar
}
if role, ok := input["role"].(string); ok {
updates["role"] = role
}
if isActive, ok := input["isActive"].(bool); ok {
updates["is_active"] = isActive
}
if err := database.DB.Model(&user).Updates(updates).Error; err != nil {
return nil, err
}
return &user, nil
}
func (r *UserResolver) DeleteUser(p graphql.ResolveParams) (interface{}, error) {
id, ok := p.Args["id"].(string)
if !ok {
return nil, errors.New("invalid user ID")
}
userID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, errors.New("invalid user ID format")
}
if err := database.DB.Delete(&models.User{}, uint(userID)).Error; err != nil {
return nil, err
}
return true, nil
}
// 关联字段解析器
func (r *UserResolver) GetUserPosts(p graphql.ResolveParams) (interface{}, error) {
user, ok := p.Source.(*models.User)
if !ok {
return nil, errors.New("invalid user source")
}
var posts []models.Post
if err := database.DB.Where("author_id = ?", user.ID).Find(&posts).Error; err != nil {
return nil, err
}
return posts, nil
}
func (r *UserResolver) GetUserComments(p graphql.ResolveParams) (interface{}, error) {
user, ok := p.Source.(*models.User)
if !ok {
return nil, errors.New("invalid user source")
}
var comments []models.Comment
if err := database.DB.Where("author_id = ?", user.ID).Find(&comments).Error; err != nil {
return nil, err
}
return comments, nil
}
func (r *UserResolver) GetUserProfile(p graphql.ResolveParams) (interface{}, error) {
user, ok := p.Source.(*models.User)
if !ok {
return nil, errors.New("invalid user source")
}
var profile models.UserProfile
if err := database.DB.Where("user_id = ?", user.ID).First(&profile).Error; err != nil {
return nil, nil // 返回 nil 而不是错误,因为 profile 是可选的
}
return &profile, nil
}
// resolvers/post.go
package resolvers
import (
"errors"
"strconv"
"strings"
"graphql-server/database"
"graphql-server/models"
"github.com/graphql-go/graphql"
)
type PostResolver struct{}
func (r *PostResolver) GetPost(p graphql.ResolveParams) (interface{}, error) {
id, ok := p.Args["id"].(string)
if !ok {
return nil, errors.New("invalid post ID")
}
postID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, errors.New("invalid post ID format")
}
var post models.Post
if err := database.DB.Preload("Author").Preload("Tags").First(&post, uint(postID)).Error; err != nil {
return nil, err
}
return &post, nil
}
func (r *PostResolver) GetPosts(p graphql.ResolveParams) (interface{}, error) {
var posts []models.Post
query := database.DB.Preload("Author").Preload("Tags")
// 处理过滤参数
if status, ok := p.Args["status"].(string); ok && status != "" {
query = query.Where("status = ?", status)
}
if authorID, ok := p.Args["authorId"].(string); ok && authorID != "" {
query = query.Where("author_id = ?", authorID)
}
if err := query.Find(&posts).Error; err != nil {
return nil, err
}
return posts, nil
}
func (r *PostResolver) CreatePost(p graphql.ResolveParams) (interface{}, error) {
input, ok := p.Args["input"].(map[string]interface{})
if !ok {
return nil, errors.New("invalid input")
}
// 从上下文获取当前用户ID(假设已经通过认证中间件设置)
ctx := p.Context
userID, ok := ctx.Value("userID").(uint)
if !ok {
return nil, errors.New("authentication required")
}
post := models.Post{
Title: input["title"].(string),
Content: input["content"].(string),
Status: input["status"].(string),
AuthorID: userID,
}
if err := database.DB.Create(&post).Error; err != nil {
return nil, err
}
// 处理标签
if tagNames, ok := input["tags"].([]interface{}); ok {
var tags []models.Tag
for _, tagName := range tagNames {
if name, ok := tagName.(string); ok {
var tag models.Tag
// 查找或创建标签
if err := database.DB.Where("name = ?", name).FirstOrCreate(&tag, models.Tag{Name: name}).Error; err != nil {
continue
}
tags = append(tags, tag)
}
}
// 关联标签
if len(tags) > 0 {
database.DB.Model(&post).Association("Tags").Append(tags)
}
}
// 重新加载完整的文章数据
database.DB.Preload("Author").Preload("Tags").First(&post, post.ID)
return &post, nil
}
// 关联字段解析器
func (r *PostResolver) GetPostAuthor(p graphql.ResolveParams) (interface{}, error) {
post, ok := p.Source.(*models.Post)
if !ok {
return nil, errors.New("invalid post source")
}
var user models.User
if err := database.DB.First(&user, post.AuthorID).Error; err != nil {
return nil, err
}
return &user, nil
}
func (r *PostResolver) GetPostComments(p graphql.ResolveParams) (interface{}, error) {
post, ok := p.Source.(*models.Post)
if !ok {
return nil, errors.New("invalid post source")
}
var comments []models.Comment
if err := database.DB.Preload("Author").Where("post_id = ?", post.ID).Find(&comments).Error; err != nil {
return nil, err
}
return comments, nil
}
func (r *PostResolver) GetPostTags(p graphql.ResolveParams) (interface{}, error) {
post, ok := p.Source.(*models.Post)
if !ok {
return nil, errors.New("invalid post source")
}
var tags []models.Tag
if err := database.DB.Model(&post).Association("Tags").Find(&tags); err != nil {
return nil, err
}
return tags, nil
}
GraphQL Schema 构建 #
// schema/schema.go
package schema
import (
"graphql-server/resolvers"
"github.com/graphql-go/graphql"
)
var userResolver = &resolvers.UserResolver{}
var postResolver = &resolvers.PostResolver{}
var commentResolver = &resolvers.CommentResolver{}
func init() {
// 定义对象类型
UserProfileType = graphql.NewObject(graphql.ObjectConfig{
Name: "UserProfile",
Description: "User profile object",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.NewNonNull(graphql.ID),
Description: "Profile ID",
},
"userId": &graphql.Field{
Type: graphql.NewNonNull(graphql.ID),
Description: "User ID",
},
"bio": &graphql.Field{
Type: graphql.String,
Description: "User bio",
},
"website": &graphql.Field{
Type: graphql.String,
Description: "User website",
},
"location": &graphql.Field{
Type: graphql.String,
Description: "User location",
},
},
})
TagType = graphql.NewObject(graphql.ObjectConfig{
Name: "Tag",
Description: "Tag object",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.NewNonNull(graphql.ID),
Description: "Tag ID",
},
"name": &graphql.Field{
Type: graphql.NewNonNull(graphql.String),
Description: "Tag name",
},
},
})
CommentType = graphql.NewObject(graphql.ObjectConfig{
Name: "Comment",
Description: "Comment object",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.NewNonNull(graphql.ID),
Description: "Comment ID",
},
"content": &graphql.Field{
Type: graphql.NewNonNull(graphql.String),
Description: "Comment content",
},
"createdAt": &graphql.Field{
Type: graphql.NewNonNull(DateType),
Description: "Comment creation date",
},
"author": &graphql.Field{
Type: graphql.NewNonNull(UserType),
Description: "Comment author",
Resolve: commentResolver.GetCommentAuthor,
},
"post": &graphql.Field{
Type: graphql.NewNonNull(PostType),
Description: "Comment post",
Resolve: commentResolver.GetCommentPost,
},
},
})
PostType = graphql.NewObject(graphql.ObjectConfig{
Name: "Post",
Description: "Post object",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.NewNonNull(graphql.ID),
Description: "Post ID",
},
"title": &graphql.Field{
Type: graphql.NewNonNull(graphql.String),
Description: "Post title",
},
"content": &graphql.Field{
Type: graphql.NewNonNull(graphql.String),
Description: "Post content",
},
"status": &graphql.Field{
Type: graphql.NewNonNull(PostStatusEnum),
Description: "Post status",
},
"createdAt": &graphql.Field{
Type: graphql.NewNonNull(DateType),
Description: "Post creation date",
},
"updatedAt": &graphql.Field{
Type: graphql.NewNonNull(DateType),
Description: "Post update date",
},
"author": &graphql.Field{
Type: graphql.NewNonNull(UserType),
Description: "Post author",
Resolve: postResolver.GetPostAuthor,
},
"comments": &graphql.Field{
Type: graphql.NewList(graphql.NewNonNull(CommentType)),
Description: "Post comments",
Resolve: postResolver.GetPostComments,
},
"tags": &graphql.Field{
Type: graphql.NewList(graphql.NewNonNull(TagType)),
Description: "Post tags",
Resolve: postResolver.GetPostTags,
},
},
})
UserType = graphql.NewObject(graphql.ObjectConfig{
Name: "User",
Description: "User object",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.NewNonNull(graphql.ID),
Description: "User ID",
},
"name": &graphql.Field{
Type: graphql.NewNonNull(graphql.String),
Description: "User name",
},
"email": &graphql.Field{
Type: graphql.NewNonNull(graphql.String),
Description: "User email",
},
"avatar": &graphql.Field{
Type: graphql.String,
Description: "User avatar URL",
},
"role": &graphql.Field{
Type: graphql.NewNonNull(UserRoleEnum),
Description: "User role",
},
"isActive": &graphql.Field{
Type: graphql.NewNonNull(graphql.Boolean),
Description: "User active status",
},
"createdAt": &graphql.Field{
Type: graphql.NewNonNull(DateType),
Description: "User creation date",
},
"updatedAt": &graphql.Field{
Type: graphql.NewNonNull(DateType),
Description: "User update date",
},
"profile": &graphql.Field{
Type: UserProfileType,
Description: "User profile",
Resolve: userResolver.GetUserProfile,
},
"posts": &graphql.Field{
Type: graphql.NewList(graphql.NewNonNull(PostType)),
Description: "User posts",
Resolve: userResolver.GetUserPosts,
},
"comments": &graphql.Field{
Type: graphql.NewList(graphql.NewNonNull(CommentType)),
Description: "User comments",
Resolve: userResolver.GetUserComments,
},
},
})
}
// 查询根类型
var QueryType = graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Description: "Root query type",
Fields: graphql.Fields{
"user": &graphql.Field{
Type: UserType,
Description: "Get user by ID",
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.ID),
Description: "User ID",
},
},
Resolve: userResolver.GetUser,
},
"users": &graphql.Field{
Type: graphql.NewList(graphql.NewNonNull(UserType)),
Description: "Get all users",
Args: graphql.FieldConfigArgument{
"role": &graphql.ArgumentConfig{
Type: UserRoleEnum,
Description: "Filter by user role",
},
"search": &graphql.ArgumentConfig{
Type: graphql.String,
Description: "Search in name and email",
},
},
Resolve: userResolver.GetUsers,
},
"post": &graphql.Field{
Type: PostType,
Description: "Get post by ID",
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.ID),
Description: "Post ID",
},
},
Resolve: postResolver.GetPost,
},
"posts": &graphql.Field{
Type: graphql.NewList(graphql.NewNonNull(PostType)),
Description: "Get all posts",
Args: graphql.FieldConfigArgument{
"status": &graphql.ArgumentConfig{
Type: PostStatusEnum,
Description: "Filter by post status",
},
"authorId": &graphql.ArgumentConfig{
Type: graphql.ID,
Description: "Filter by author ID",
},
},
Resolve: postResolver.GetPosts,
},
},
})
// 变更根类型
var MutationType = graphql.NewObject(graphql.ObjectConfig{
Name: "Mutation",
Description: "Root mutation type",
Fields: graphql.Fields{
"createUser": &graphql.Field{
Type: graphql.NewNonNull(UserType),
Description: "Create a new user",
Args: graphql.FieldConfigArgument{
"input": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(CreateUserInputType),
Description: "User input data",
},
},
Resolve: userResolver.CreateUser,
},
"updateUser": &graphql.Field{
Type: graphql.NewNonNull(UserType),
Description: "Update an existing user",
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.ID),
Description: "User ID",
},
"input": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(UpdateUserInputType),
Description: "User update data",
},
},
Resolve: userResolver.UpdateUser,
},
"deleteUser": &graphql.Field{
Type: graphql.NewNonNull(graphql.Boolean),
Description: "Delete a user",
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.ID),
Description: "User ID",
},
},
Resolve: userResolver.DeleteUser,
},
"createPost": &graphql.Field{
Type: graphql.NewNonNull(PostType),
Description: "Create a new post",
Args: graphql.FieldConfigArgument{
"input": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(CreatePostInputType),
Description: "Post input data",
},
},
Resolve: postResolver.CreatePost,
},
},
})
// 创建 Schema
var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: QueryType,
Mutation: MutationType,
})
服务器启动 #
// main.go
package main
import (
"context"
"log"
"net/http"
"graphql-server/database"
"graphql-server/schema"
"github.com/gin-gonic/gin"
"github.com/graphql-go/graphql"
"github.com/graphql-go/handler"
)
func main() {
// 初始化数据库
database.InitDatabase()
// 创建 Gin 路由器
r := gin.Default()
// CORS 中间件
r.Use(func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
})
// 认证中间件(简化版)
authMiddleware := func(c *gin.Context) {
// 这里应该实现真正的JWT验证
// 为了演示,我们假设用户ID为1
c.Set("userID", uint(1))
c.Next()
}
// GraphQL 处理器
h := handler.New(&handler.Config{
Schema: &schema.Schema,
Pretty: true,
GraphiQL: true,
Playground: true,
})
// GraphQL 端点
r.POST("/graphql", authMiddleware, func(c *gin.Context) {
// 将用户信息添加到上下文
ctx := context.WithValue(c.Request.Context(), "userID", c.GetUint("userID"))
c.Request = c.Request.WithContext(ctx)
h.ServeHTTP(c.Writer, c.Request)
})
r.GET("/graphql", func(c *gin.Context) {
h.ServeHTTP(c.Writer, c.Request)
})
// 健康检查端点
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{
"status": "ok",
"message": "GraphQL server is running",
})
})
log.Println("GraphQL server is running on http://localhost:8080/graphql")
log.Println("GraphiQL is available at http://localhost:8080/graphql")
if err := r.Run(":8080"); err != nil {
log.Fatal("Failed to start server:", err)
}
}
测试查询示例 #
启动服务器后,可以在 GraphiQL 界面中测试以下查询:
查询用户列表 #
query {
users {
id
name
email
role
createdAt
posts {
id
title
status
}
}
}
创建用户 #
mutation {
createUser(
input: {
name: "Alice Johnson"
email: "[email protected]"
role: USER
avatar: "https://example.com/alice.jpg"
}
) {
id
name
email
role
createdAt
}
}
创建文章 #
mutation {
createPost(
input: {
title: "My First GraphQL Post"
content: "This is the content of my first GraphQL post."
status: PUBLISHED
tags: ["graphql", "go", "tutorial"]
}
) {
id
title
content
status
author {
name
email
}
tags {
id
name
}
}
}
复杂查询 #
query {
posts(status: PUBLISHED) {
id
title
createdAt
author {
name
email
profile {
bio
website
}
}
comments {
id
content
author {
name
}
}
tags {
name
}
}
}
通过这个完整的实现,你已经构建了一个功能完整的 GraphQL 服务器,支持查询、变更和复杂的关联数据获取。在下一节中,我们将学习如何开发 GraphQL 客户端应用。