内容简介:项目地址:二话不说,先贴官网:
项目地址: https://github.com/unrotten/hello-world-web
开始之前,关于GraphQL的介绍
二话不说,先贴官网: https://graphql.org
如图所见,GraphQL可以分为三个部分:一是对数据的描述,称为类型系统,通过定义不同的type,来描述数据之间的关系。这其实很容易理解,可以直接类比为我们再 Go 中定义的struct,不同的是,在GraphQL中,每一个字段都是一个type。二是请求;在之前,我们定义了三个type,query,matutation和subscription,其实就是请求的方式。这里可以发现,其实我们所请求的东西,就是type,即我们对数据的描述。那么请求的内容也显而易见,应该是我们定义好的type。三是返回的结果;GraphQL是对数据的描述,从定义,到请求,再到结果,都不外乎是GraphQL中的type。
通过以上的三部分,不难发现,GraphQL的整一个流程,无非就是开发者定义好一系列的type,使用者拿到这些type,然后根据自己所需,去请求所要的type,最终获得返回。那么这里就体现了GraphQL的宗旨,即由调用方去决定请求什么数据,而提供方只需按照GraphQL的方式,将他所拥有的数据,描述出来即可。
GraphQL库使用入门
我们首先来看作者的示例程序:
package main import ( "encoding/json" "fmt" "log" "github.com/graphql-go/graphql" ) func main() { // Schema fields := graphql.Fields{ "hello": &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "world", nil }, }, } rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields} schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)} schema, err := graphql.NewSchema(schemaConfig) if err != nil { log.Fatalf("failed to create new schema, error: %v", err) } // Query query := ` { hello } ` params := graphql.Params{Schema: schema, RequestString: query} r := graphql.Do(params) if len(r.Errors) > 0 { log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors) } rJSON, _ := json.Marshal(r) fmt.Printf("%s \n", rJSON) // {"data":{"hello":"world"}} }
我们从底部看起,可以看到我们编写的请求hello通过graphql.Params构建了一个请求参数,并交由graphql.Do方法执行。而Params中,定义了一个Schema。再往上看,可以知道Schema由graphql.NewSchema(schemaConfig)声明,而schemaConfig的Query参数接收了一个Object指针。这个Object就是我们对数据的定义,它在rootQuery中被描述为,名称为RootQuery,字段为hello的类型。其中字段hello就是我们具体的数据,其类型为Field。对于每个字段,我们至少需要定义他的类型Type,在这里hello的类型是一个String。Resolve是对字段的解析函数,字段的值由Resolve函数返回。
定义用户数据
首先我们需要分析,关于用户,我们需要什么数据,以下列了几个最基础的:
- 用户注册:用户名,邮箱,密码
- 用户登录:用户名/邮箱,密码
- 获取当前登录用户信息:当前登录用户详细信息
- 获取指定用户信息: 指定用户详细信息
- 获取用户列表:用户列表
- 修改用户信息:用户详细信息
在 controller
目录下新建 user.go
文件:
package controller import ( "github.com/graphql-go/graphql" "github.com/graphql-go/relay" ) var gender = graphql.NewEnum(graphql.EnumConfig{ Name: "Gender", Values: graphql.EnumValueConfigMap{ "man": {Value: "man", Description: "男"}, "woman": {Value: "woman", Description: "女"}, "unknown": {Value: "unknown", Description: "保密"}, }, Description: "性别", }) var userState = graphql.NewEnum(graphql.EnumConfig{ Name: "UserState", Values: graphql.EnumValueConfigMap{ "unsign": {Value: "unsign", Description: "未认证"}, "normal": {Value: "normal", Description: "正常"}, "forbidden": {Value: "forbidden", Description: "禁止发言"}, "freeze": {Value: "freeze", Description: "冻结"}, }, Description: "用户状态", }) var userType = graphql.NewObject(graphql.ObjectConfig{ Name: "User", Fields: graphql.Fields{ "id": relay.GlobalIDField("User", nil), "username": {Type: graphql.String, Description: "用户名"}, "email": {Type: graphql.String, Description: "邮箱"}, "avatar": {Type: graphql.String, Description: "头像"}, "gender": {Type: gender, Description: "性别"}, "introduce": {Type: graphql.String, Description: "个人简介"}, "state": {Type: userState, Description: "状态"}, "root": {Type: graphql.Boolean, Description: "管理员"}, }, Description: "用户数据", }) var userConnectionDefinition = relay.ConnectionDefinitions(relay.ConnectionConfig{ Name: "User", NodeType: userType, }) func registerUserType() { queryType.AddFieldConfig("GetUserList", &graphql.Field{ Type: userConnectionDefinition.ConnectionType, Args: relay.ConnectionArgs, Resolve: nil, Description: "用户列表", }) queryType.AddFieldConfig("GetUser", &graphql.Field{ Type: userType, Args: graphql.FieldConfigArgument{ "id": {Type: graphql.ID, Description: "ID"}, "username": {Type: graphql.String, Description: "用户名"}, }, Resolve: nil, Description: "获取用户信息", }) queryType.AddFieldConfig("CurrentUser", &graphql.Field{ Type: userType, Resolve: nil, Description: "获取当前登录用户信息", }) mutationType.AddFieldConfig("CreatUser", &graphql.Field{ Type: userType, Args: graphql.FieldConfigArgument{ "username": {Type: graphql.NewNonNull(graphql.String), Description: "用户名"}, "email": {Type: graphql.NewNonNull(graphql.String), Description: "邮箱"}, "password": {Type: graphql.NewNonNull(graphql.String), Description: "密码"}, }, Resolve: nil, Description: "注册新用户", }) mutationType.AddFieldConfig("SignIn", &graphql.Field{ Type: userType, Args: graphql.FieldConfigArgument{ "username": {Type: graphql.NewNonNull(graphql.String), Description: "用户名"}, "password": {Type: graphql.NewNonNull(graphql.String), Description: "密码"}, }, Resolve: nil, Description: "用户登录", }) mutationType.AddFieldConfig("UpdateUser", &graphql.Field{ Type: userType, Args: graphql.FieldConfigArgument{ "username": {Type: graphql.String, Description: "用户名"}, "email": {Type: graphql.String, Description: "邮箱"}, "avatar": {Type: graphql.String, Description: "头像"}, "gender": {Type: gender, Description: "性别"}, "introduce": {Type: graphql.String, Description: "个人简介"}, }, Resolve: nil, Description: "修改用户信息", }) }
首先我们需要知道,Field在GraphQL中的定义:
type Field struct { Name string `json:"name"` // used by graphlql-relay Type Output `json:"type"` Args FieldConfigArgument `json:"args"` Resolve FieldResolveFn `json:"-"` DeprecationReason string `json:"deprecationReason"` Description string `json:"description"` }
Name是字段名,Type是所属类型,Args即获取本字段所需的参数,Resolve是解析函数。
我们首先定义了两个枚举类型,分别是gender和userState。
在userType中,我们使用了 relay 库,并将字段id定义为GlobalIDField,这是为了便于使用relay提供的分页功能。在registerUserType函数中,我们向queryType注册了两个字段GetUserList和GetUser,分别用于获取用户列表和获取单个用户的信息。其中参数relay.ConnectionArgs定义为:
var ConnectionArgs = graphql.FieldConfigArgument{ "before": &graphql.ArgumentConfig{ Type: graphql.String, }, "after": &graphql.ArgumentConfig{ Type: graphql.String, }, "first": &graphql.ArgumentConfig{ Type: graphql.Int, }, "last": &graphql.ArgumentConfig{ Type: graphql.Int, }, }
我们定义了一个mutationType的字段CreateUser,用于新用户的注册。
可以看到,所有定义的类型中,解析函数Resolve目前均为空,后面我们会将具体的处理逻辑加上去。
修改 graphql.go
文件,调用registerUserType注册用户类型字段:
queryType = graphql.NewObject(graphql.ObjectConfig{Name: "Query", Fields: graphql.Fields{}}) mutationType = graphql.NewObject(graphql.ObjectConfig{Name: "Mutation", Fields: graphql.Fields{}}) subscriptType = graphql.NewObject(graphql.ObjectConfig{Name: "Subscription", Fields: graphql.Fields{ "test": { Name: "test", Type: graphql.String, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return "test", nil }, Description: "test", }, }}) registerUserType() schemaConfig := graphql.SchemaConfig{ Query: queryType, Mutation: mutationType, Subscription: subscriptType, }
启动项目,进入GraphiQL:
定义文章数据
照例我们需要分析关于文章,我们需要什么数据:
- 获取指定文章内容:文章的标题,内容
- 获取文章列表:文章的标题列表,也可以包含简介
- 新增文章:文章标题,内容,作者,标签
- 修改文章:文章标题,内容,作者,标签,ID
- 删除文章:文章ID
我们在 controller
目录下新建文件 article.go
:
package controller import ( "github.com/graphql-go/graphql" "github.com/graphql-go/relay" ) var articleState = graphql.NewEnum(graphql.EnumConfig{ Name: "ArticleState", Values: graphql.EnumValueConfigMap{ "unaudited": {Value: "unaudited", Description: "未审核"}, "online": {Value: "online", Description: "已上线"}, "offline": {Value: "offline", Description: "已下线"}, "deleted": {Value: "deleted", Description: "已删除"}, }, Description: "文章状态", }) var articleType = graphql.NewObject(graphql.ObjectConfig{ Name: "Article", Fields: graphql.Fields{ "id": relay.GlobalIDField("Article", nil), "sn": {Type: graphql.NewNonNull(graphql.String), Description: "序号"}, "title": {Type: graphql.NewNonNull(graphql.String), Description: "标题"}, "uid": {Type: graphql.NewNonNull(graphql.ID), Description: "作者ID"}, "cover": {Type: graphql.String, Description: "封面"}, "content": {Type: graphql.String, Description: "文章内容"}, "tags": {Type: graphql.NewList(graphql.String), Description: "标签"}, "state": {Type: graphql.NewNonNull(articleState), Description: "状态"}, }, Description: "文章", }) var articleConnectionDefinition = relay.ConnectionDefinitions(relay.ConnectionConfig{ Name: "Article", NodeType: articleType, }) func registerArticleType() { queryType.AddFieldConfig("GetArticle", &graphql.Field{ Type: articleType, Args: graphql.FieldConfigArgument{"id": {Type: graphql.NewNonNull(graphql.ID), Description: "ID"}}, Resolve: nil, Description: "获取指定文章", }) queryType.AddFieldConfig("Articles", &graphql.Field{ Type: articleConnectionDefinition.ConnectionType, Args: relay.NewConnectionArgs(graphql.FieldConfigArgument{ "title": {Type: graphql.String, Description: "标题"}, "uid": {Type: graphql.ID, Description: "作者ID"}, "content": {Type: graphql.String, Description: "内容"}, "tags": {Type: graphql.NewList(graphql.String), Description: "标签"}, }), Resolve: nil, Description: "获取文章列表", }) mutationType.AddFieldConfig("CreateArticle", &graphql.Field{ Type: articleType, Args: graphql.FieldConfigArgument{ "title": {Type: graphql.NewNonNull(graphql.String), Description: "标题"}, "cover": {Type: graphql.String, Description: "封面"}, "content": {Type: graphql.String, Description: "文章内容"}, "tags": {Type: graphql.NewList(graphql.String), Description: "标签"}, }, Resolve: nil, Description: "新增文章", }) mutationType.AddFieldConfig("UpdateArticle", &graphql.Field{ Type: articleType, Args: graphql.FieldConfigArgument{ "id": {Type: graphql.NewNonNull(graphql.ID), Description: "ID"}, "title": {Type: graphql.NewNonNull(graphql.String), Description: "标题"}, "cover": {Type: graphql.String, Description: "封面"}, "content": {Type: graphql.String, Description: "文章内容"}, "tags": {Type: graphql.NewList(graphql.String), Description: "标签"}, }, Resolve: nil, Description: "修改文章", }) mutationType.AddFieldConfig("DeleteArticle", &graphql.Field{ Type: articleType, Args: graphql.FieldConfigArgument{"id": {Type: graphql.NewNonNull(graphql.ID), Description: "ID"}}, Resolve: nil, Description: "删除文章", }) }
修改 graphql.go
文件:
registerUserType() registerArticleType() schemaConfig := graphql.SchemaConfig{ Query: queryType, Mutation: mutationType, Subscription: subscriptType, } var err error schema, err = graphql.NewSchema(schemaConfig) if err != nil { panic(err) } h := handler.New(&handler.Config{ Schema: &schema, Pretty: true, GraphiQL: false, Playground: true, })
在这里我们不仅新注册了文章的type,同时也将GraphQL的调试界面,从GrapiQL换成了Playground,效果如下:
到这里对于GraphQL API的定义方式都有一定了解了。剩余未定义的包括:评论,评论回复,赞,标签,用户计数,文章扩展等数据尚未定义,可以自己动手尝试。我们定义完成之后,可以从Playground中,下载graphql库根据定义,生成的.graphql文件:
type Article { content: String count: ArticleEx cover: String id: ID! sn: String! state: ArticleState! tags: [String] title: String! uid: ID! } type ArticleConnection { edges: [ArticleEdge] pageInfo: PageInfo! } type ArticleEdge { cursor: String! node: Article } type ArticleEx { aid: ID! cmtNum: Int! viewNum: Int! zanNum: Int! } enum ArticleState { unaudited online offline deleted } type Comment { aid: String! content: String! floor: Int! id: ID! replies: [CommentReply] state: ArticleState! uid: String! zanNum: Int! } type CommentConnection { edges: [CommentEdge] pageInfo: PageInfo! } type CommentEdge { cursor: String! node: Comment } type CommentReply { cid: ID! content: String! id: ID! state: ArticleState! uid: ID! } enum Gender { man woman unknown } type Mutation { AddTag(name: String!): Tag CancelFollow(uid: ID!): UserFollow CancelZan(id: ID!): Boolean Comment( aid: ID! content: String! ): Comment CreatUser( username: String! email: String! password: String! ): User CreateArticle( title: String! cover: String content: String tags: [String] ): Article DeleteArticle(id: ID!): Article DeleteComment(id: ID!): Comment DeleteReply(id: ID!): CommentReply Follow(uid: ID!): UserFollow Reply( cid: ID! content: String! ): CommentReply SignIn( username: String! password: String! ): User UpdateArticle( id: ID! title: String! cover: String content: String tags: [String] ): Article UpdateUser( gender: Gender introduce: String username: String email: String avatar: String ): User Zan( objtype: Objtype! objid: ID! ): Zan } enum Objtype { reply article comment } type PageInfo { endCursor: String hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String } type Query { Articles( title: String uid: ID content: String tags: [String] last: Int before: String after: String first: Int ): ArticleConnection Comments( aid: ID! before: String after: String first: Int last: Int ): CommentConnection CurrentUser: User GetArticle(id: ID!): Article GetUser( id: ID username: String ): User GetUserList( before: String after: String first: Int last: Int ): UserConnection Tag(name: String): [Tag] } type Subscription { test: String } type Tag { id: ID! name: String! } type User { avatar: String! email: String! fans: [User] follows: [User] gender: Gender! id: ID! introduce: String root: Boolean! state: UserState! userCount: UserCount username: String! } type UserConnection { edges: [UserEdge] pageInfo: PageInfo! } type UserCount { articleNum: Int! fansNum: Int! followNum: Int! uid: ID! words: Int! zanNum: Int! } type UserEdge { cursor: String! node: User } type UserFollow { fuid: ID! id: ID! uid: ID! } enum UserState { unsign normal forbidden freeze } type Zan { id: ID! objid: ID! objtype: Objtype! uid: ID! }
其实这个时候,如果我们是前后端分开做的,已经可以开始让前端一起进行了。但是实际上——至少我不是,所以后续我们会将后端的Resolve初步完成,才会开始前端的工作。
作者个人博客地址: https://unrotten.org
作者微信公众号:
以上所述就是小编给大家介绍的《go+typescript+graphQL+react构建简书网站(二) 编写GraphQL API》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 从 0 到 1 再到 100, 搭建、编写、构建一个前端项目
- Go+typescript+GraphQL+react构建简书网站(三) 编写Model
- 使用 Dingo API 扩展包快速构建 Laravel RESTful API(二) —— 编写第一个 API 接口
- [ Laravel从入门到精通 ] 编写 JSON API —— 基于资源控制器和 API 资源类快速构建 API 接口
- 基于顺丰同城接口编写sdk,java三方sdk编写思路
- 使用 Clojure 编写 OpenWhisk 操作,第 1 部分: 使用 Lisp 方言为 OpenWhisk 编写简明的代码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。