使用 Go 和 gqlgen 实现 GraphQL API:实战指南
使用 Go 和 gqlgen 实现 GraphQL API:实战指南
在本文中,我将分享如何使用 Go 语言和 gqlgen 框架实现一个完整的 GraphQL API。我们将构建一个包含用户、文章和评论功能的博客系统 API。
技术栈
- Go
- gqlgen (GraphQL 框架)
- MySQL (数据存储)
- Redis (缓存,可选)
项目结构
go_graphql/
├── config/
│ └── database.go # 数据库配置
├── graph/
│ ├── model/ # 数据模型
│ ├── schema.graphqls # GraphQL schema
│ └── schema.resolvers.go # Resolver 实现
├── server.go # 主程序入口
└── gqlgen.yml # gqlgen 配置文件
GraphQL Schema 设计
首先,我们需要定义 GraphQL schema,这是整个 API 的基础:
type User {
id: ID!
username: String!
email: String!
avatar: String
createdAt: String!
posts: [Post!]
comments: [Comment!]
}
type Post {
id: ID!
title: String!
content: String!
author: User!
category: Category!
createdAt: String!
updatedAt: String
comments: [Comment!]
images: [Image!]
}
type Comment {
id: ID!
content: String!
author: User!
post: Post!
createdAt: String!
}
type Query {
users: [User!]!
user(id: ID!): User
posts(categoryId: ID): [Post!]!
post(id: ID!): Post
}
type Mutation {
createUser(input: CreateUserInput!): User!
createPost(input: CreatePostInput!): Post!
createComment(input: CreateCommentInput!): Comment!
}
Resolver 实现
下面是一个完整的用户查询 resolver 实现示例:
// Users resolver 实现
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
rows, err := config.DB.Query(`
SELECT id, username, email, avatar, created_at
FROM users`)
if err != nil {
return nil, fmt.Errorf("failed to query users: %v", err)
}
defer rows.Close()
var users []*model.User
for rows.Next() {
var user model.User
var createdAt time.Time
err := rows.Scan(&user.ID, &user.Username, &user.Email,
&user.Avatar, &createdAt)
if err != nil {
return nil, fmt.Errorf("failed to scan user: %v", err)
}
user.CreatedAt = createdAt.Format(time.RFC3339)
users = append(users, &user)
}
return users, nil
}
// User 类型的 posts 字段 resolver
func (r *userResolver) Posts(ctx context.Context, obj *model.User) ([]*model.Post, error) {
rows, err := config.DB.Query(`
SELECT p.id, p.title, p.content, p.created_at, p.updated_at,
p.category_id, p.author_id
FROM posts p
WHERE p.author_id = ?`, obj.ID)
if err != nil {
return nil, fmt.Errorf("failed to query posts: %v", err)
}
defer rows.Close()
var posts []*model.Post
for rows.Next() {
var post model.Post
var createdAt, updatedAt time.Time
var categoryID, authorID string
err := rows.Scan(&post.ID, &post.Title, &post.Content,
&createdAt, &updatedAt, &categoryID, &authorID)
if err != nil {
return nil, fmt.Errorf("failed to scan post: %v", err)
}
post.CreatedAt = createdAt.Format(time.RFC3339)
updatedAtStr := updatedAt.Format(time.RFC3339)
post.UpdatedAt = &updatedAtStr
posts = append(posts, &post)
}
return posts, nil
}
代码生成
gqlgen 是一个强大的 GraphQL 代码生成工具,它可以:
- 根据 schema 自动生成 Go 类型
- 生成所有必要的接口和类型定义
- 保持自定义实现代码不变
使用以下命令生成代码:
go run github.com/99designs/gqlgen generate
生成的代码包括:
graph/generated/generated.go
: 包含所有生成的接口和类型graph/model/models_gen.go
: 包含根据 schema 生成的 Go 结构体graph/schema.resolvers.go
: 包含 resolver 实现的框架代码
最佳实践
-
类型安全:利用 Go 的类型系统和 gqlgen 的代码生成确保类型安全
-
错误处理:
if err != nil { return nil, fmt.Errorf("failed to query users: %v", err) }
-
资源清理:使用 defer 确保资源正确释放
defer rows.Close()
-
时间处理:统一使用 RFC3339 格式处理时间
createdAt.Format(time.RFC3339)
-
空值处理:对可选字段使用指针类型
updatedAtStr := updatedAt.Format(time.RFC3339) post.UpdatedAt = &updatedAtStr