GraphQL api服务器
选择 GraphQL 的好处?
- 避免对 API 进行多个版本控制
- 客户端可以只获取其需要的字段,而服务端不用做特殊的处理
- 避免多次调用API以获取相关联数据。GraphQL允许了在单个请求中获取其相关联数据
- 提高其程序性能
GraphQL 架构

- Schema: 定义了数据的类型、数据之间的关系以及允许客户端进行的操作,以及每个操作的参数和返回类型。
- Queries: 用于从GraphQL服务器检索数据。
- Mutations: 用于对服务器进行写操作,比如新增、修改和删除等操作。
使用 golang Gin 创建 GraphQL api 服务器
$ echo '初始项目环境'
$ mkdir graphql_test_server
$ cd graphql_test_server
$ go mod init github.com/sy-vendor/graphql_test_server
$ go get github.com/99designs/gqlgen
$ go get -u github.com/gin-gonic/gin
$ go get -u github.com/jinzhu/gorm
$ echo '构建服务器'
$ go run github.com/99designs/gqlgen init
编写 GraphQL
文件graph/schema.graphqls
type Question{
id: String!
question_text: String!
pub_date: String!
choices: [Choice]
}
type Choice{
id: String!
question: Question!
question_id: String!
choice_text: String!
}
type Query {
questions: [Question]!
choices: [Choice]!
}
input QuestionInput {
question_text: String!
pub_date: String!
}
input ChoiceInput {
question_id: String!
choice_text: String!
}
type Mutation {
createQuestion(input: QuestionInput!): Question!
createChoice(input: ChoiceInput): Choice!
}
运行下面的命令来更新schema实现
$ rm graph/schema.resolvers.go && gqlgen generate
打开 graph/schema.resolvers.go,查看是否像如下生成了查询func (r *queryResolver) Questions
和func (r *queryResolver) Choices
以及func (r *mutationResolver) CreateQuestion
。func (r *mutationResolver) CreateChoice
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"
"github.com/sy-vendor/gin-graphql-postgres/graph/generated"
"github.com/sy-vendor/gin-graphql-postgres/graph/model"
)
func (r *mutationResolver) CreateQuestion(ctx context.Context, input model.QuestionInput) (*model.Question, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *mutationResolver) CreateChoice(ctx context.Context, input *model.ChoiceInput) (*model.Choice, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *queryResolver) Questions(ctx context.Context) ([]*model.Question, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *queryResolver) Choices(ctx context.Context) ([]*model.Choice, error) {
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 }
使用’gorm’设置数据库orm
创建文件db/main.go
并实现以下代码
package database
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"graphql_test_server/graph/model"
)
type dbConfig struct {
host string
port int
user string
dbname string
password string
}
var config = dbConfig{"localhost", 5432, "postgres", "test", "root"}
func getDatabaseUrl() string {
return fmt.Sprintf(
"host=%s port=%d user=%s dbname=%s password=%s",
config.host, config.port, config.user, config.dbname, config.password)
}
func GetDatabase() (*gorm.DB, error) {
db, err := gorm.Open("postgres", getDatabaseUrl())
return db, err
}
func RunMigrations(db *gorm.DB) {
if !db.HasTable(&model.Question{}) {
db.CreateTable(&model.Question{})
}
if !db.HasTable(&model.Choice{}) {
db.CreateTable(&model.Choice{})
db.Model(&model.Choice{}).AddForeignKey("question_id", "questions(id)", "CASCADE", "CASCADE")
}
}
完善 GraphQL 解析器
编写graph/schema.resolvers.go
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"
"log"
database "graphql_test_server/db"
"graphql_test_server/graph/generated"
"graphql_test_server/graph/model"
)
func (r *mutationResolver) CreateQuestion(ctx context.Context, input model.QuestionInput) (*model.Question, error) {
db, err := database.GetDatabase()
if err != nil {
log.Println("Unable to connect to database", err)
return nil, err
}
defer db.Close()
fmt.Println("input", input.QuestionText, input.PubDate)
question := model.Question{}
question.QuestionText = input.QuestionText
question.PubDate = input.PubDate
db.Create(&question)
return &question, nil
}
func (r *mutationResolver) CreateChoice(ctx context.Context, input *model.ChoiceInput) (*model.Choice, error) {
db, err := database.GetDatabase()
if err != nil {
log.Println("Unable to connect to database", err)
return nil, err
}
defer db.Close()
choice := model.Choice{}
question := model.Question{}
choice.QuestionID = input.QuestionID
choice.ChoiceText = input.ChoiceText
db.First(&question, choice.QuestionID)
choice.Question = &question
db.Create(&choice)
return &choice, nil
}
func (r *queryResolver) Questions(ctx context.Context) ([]*model.Question, error) {
db, err := database.GetDatabase()
if err != nil {
log.Println("Unable to connect to database", err)
return nil, err
}
defer db.Close()
db.Find(&r.questions)
for _, question := range r.questions {
var choices []*model.Choice
db.Where(&model.Choice{QuestionID: question.ID}).Find(&choices)
question.Choices = choices
}
return r.questions, nil
}
func (r *queryResolver) Choices(ctx context.Context) ([]*model.Choice, error) {
db, err := database.GetDatabase()
if err != nil {
log.Println("Unable to connect to database", err)
return nil, err
}
defer db.Close()
db.Find(&r.choices)
return r.choices, 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 }
最后编写server.go
package main
import (
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/gin-gonic/gin"
"graphql_test_server/graph"
"graphql_test_server/graph/generated"
)
const defaultPort = ":8080"
// Defining the Graphql handler
func graphqlHandler() gin.HandlerFunc {
// NewExecutableSchema and Config are in the generated.go file
// Resolver is in the resolver.go file
h := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
return func(c *gin.Context) {
h.ServeHTTP(c.Writer, c.Request)
}
}
// Defining the Playground handler
func playgroundHandler() gin.HandlerFunc {
h := playground.Handler("GraphQL", "/query")
return func(c *gin.Context) {
h.ServeHTTP(c.Writer, c.Request)
}
}
func main() {
r := gin.Default()
r.POST("/query", graphqlHandler())
r.GET("/", playgroundHandler())
r.Run(defaultPort)
}
现在使用如下命令准备测试 GraphQL api 服务器
$ go run server.go

GraphQL 中 mutation
mutation {
createQuestion(input: {question_text: "What is your name ?", pub_date: "2023-03-12"}){
id
question_text
}
}
执行上述查询后,将得到如下所示的 JSON 数据
{
"data": {
"createQuestion": {
"id": "3",
"question_text": "What is your name ?"
}
}
}
在上面用数据创建{question_text: "What is your name ?", pub_date: "2020-04-27"}
,创建后要求它返回id
, question_text
。如果只想要 id 那么可以question_text
从查询中删除。
相应的cURL
请求如下:
curl 'http://localhost:8080/query' \
-H 'Connection: keep-alive' \
-H 'accept: */*' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36' \
-H 'content-type: application/json' \
-H 'Origin: http://localhost:8080' \
-H 'Sec-Fetch-Site: same-origin' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Referer: http://localhost:8080/' \
-H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8'\
--data-binary '{"operationName":null,"variables":{},"query":"mutation {\n createQuestion(input: {question_text: \"What is your name ?\", pub_date: \"2020-03-12\"}) {\n id\n question_text\n }\n}\n"}' \
--compressed
同样方式创建Choice
:
mutation {
createChoice(input: {question_id: "3", choice_text: "Agiliq"}){
id
question{
id
question_text
}
choice_text
}
}
GraphQL 中 query
编写一个 graphql 的query
query{
questions{
id
question_text
choices{
id
choice_text
}
}
}
按上述query条件请求将返回如下:
{
"data": {
"questions": [
{
"id": "3",
"question_text": "What is your name ?",
"choices": [
{
"id": "31",
"choice_text": "Agiliq"
}
]
}
]
}
}
源码: Github – graphql_test_server
发表评论
要发表评论,您必须先登录。