Graphql go
By ppcamp
| 14 minutes read | 2843 wordsWhat’s this so well know graphql?
Go’tcha, I won’t discuss about this at all LoL.
Take a look in those articles below:
- https://www.moesif.com/blog/technical/graphql/REST-vs-GraphQL-APIs-the-good-the-bad-the-ugly/
- https://blog.logrocket.com/why-you-shouldnt-use-graphql/
- https://www.apollographql.com/docs/react/data/operation-best-practices/
- https://graphql.org/
- https://www.youtube.com/watch?v=epKhPB9PJqY&ab_channel=Simplilearn
But, personally, what I think about it:
Good points:
- It can be interesting when the amount of data of each request is relevant (mobile/lightweight apps)
- It can be interesting doing to the reuse of existent resolvers for our types (you’ll see an example in this project)
- It’s easier to the backend developer to handle with the errors (without worring about status codes, padronized response bodies and go on)
- It’s interesting to have a panel where you can see all available types/queries/mutations and its descriptions
Bad points:
- It’s “a pain in the ass” be limited about the folder structure to the resolver and types (due to the “cyclic problem”)
- If you mess it up when doing the type, you can allow a huge query that can be used, which will took very processing time.
- Compared to the default http, problably you’ll need much more resources in your application.
Project
To better understand what’s the biggest bennefits and how to use a graphql approach I’ll show, in the next sections, how I developed a simple graphql server.
To be more intuitive I’ll follow a flux defined by me self, if you got lost in some part you can just use the project and check it out the full code.
NOTE that I won’t explain every little detail, I’ll give focus on some of my thoughts during the deployment.
Project structure
At first, I’ve tried the “components” folder structure, I mean, I’ve tried to split the handlers into each specific folder, however, due to the “resolvers” you’ll problably get stuck when you tried to create a field that’s actually another type and has it’s own resolve. This will cause a cyclic import problem and, since golang doesn’t have a fancy way to bypass this like NestJs, i think that’s the not best practice and furthermore, I’ll changed it in the future for something like:
By know, the handlers 1 of this code is something like this:
And I’ll change it to be more like this (which i think that will solve the cyclic dependency):
Let’s skip this small talk and get in into the point.
Hows the project currently
By now, the project has the following folder structure:
Let’s dive in.
CMD
In this folder/package, we have all methods used by the main function, which includes registering http middlewares and endpoints and go on.
To manage all app variables I’m using the cli and yes, I won’t be doing many handwriting job, I mean, if already exists a naive solution, WHY SHOULD I TAKE THE HARDER WAY?.
Of course, sometimes you just need 1 variable and, get a package just to this won’t be justified. However, that’s not the case here. I wan’t to have full control of my variables and have the possibility of change each one of them without needing to rebuild all my project. So, my main.go is:
1package main
2
3import (
4 "os"
5
6 "github.com/ppcamp/go-graphql-with-auth/internal/config"
7 postgres "github.com/ppcamp/go-graphql-with-auth/internal/repository"
8 "github.com/sirupsen/logrus"
9 "github.com/urfave/cli/v2"
10)
11
12func main() {
13 app := cli.NewApp()
14 app.Name = "go-graphql-user"
15 app.Usage = "Build the file and execute it and then, make some graphql calls"
16 app.Flags = config.Flags
17 app.Action = run
18 app.Run(os.Args)
19}
20
21func run(c *cli.Context) error {
22 config.Setup()
23
24 // if config.App.Migrate {
25 // migrate := migrations.SetupMigrations(c.App.Name, config.Database.Url)
26 // err := migrate.Up()
27 // if err != nil {
28 // logrus.WithError(err).Fatal("failed to migrate the data")
29 // }
30 // }
31
32 storage, err := postgres.NewStorage()
33 if err != nil {
34 logrus.Fatal("couldn't connect to databaseql")
35 }
36
37 r := SetupEngine(storage)
38 r.Run(config.App.Address)
39 return nil
40}
The first question that you should be doing is: WHY this code is commented and yes, I didn’t finished 😁
The points that you should pay attention is the config.Flags and the run function.
In the run function I’ll be instantiating every service that will need to be passed through our endpoints and some stuffs like that, e.g, our database.
To build our server, I’m using the gin framework. Again, DO I NEED TO USE GIN? NO, but i’ll.
The middleware.go is simple to unterstand, so I’ll skip it.
Endpoints
In the endpoints.go is where I declare my router/http server and it’s endpoints.
The main part of this function is the schema and the controllers.
The userController and the appController, like I told before, problably need to be replaced by I single unique controller that will have all handlers.
1package main
2
3import (
4 "github.com/gin-contrib/gzip"
5 "github.com/gin-gonic/gin"
6 "github.com/ppcamp/go-graphql-with-auth/internal/config"
7 "github.com/ppcamp/go-graphql-with-auth/internal/controllers/app"
8 "github.com/ppcamp/go-graphql-with-auth/internal/controllers/user"
9 "github.com/ppcamp/go-graphql-with-auth/internal/helpers/graphql"
10 postgres "github.com/ppcamp/go-graphql-with-auth/internal/repository"
11 "github.com/ppcamp/go-graphql-with-auth/internal/services/jwt"
12)
13
14func SetupEngine(storage postgres.Storage) *gin.Engine {
15 router := gin.New()
16
17 // middlewares
18 registerMiddlewares(router)
19
20 // handlers
21 schema := graphql.NewSchemaManager()
22 userController := user.NewUserControllerBuilder(storage)
23 appController := app.NewAppController(storage)
24
25 // Endpoints unprotected
26 schema.RegisterQuery("users", userController.QueryUsers())
27 schema.RegisterMutation("createUser", userController.CreateUser())
28 schema.RegisterMutation("login", userController.Login())
29
30 // Endpoints protected
31 schema.RegisterAuthenticatedQuery("app", appController.QueryAppStatus())
32 schema.RegisterAuthenticatedMutation("updateUser", userController.EditUser())
33
34 // register
35 router.Any("/graphql", schema.Handler())
36
37 return router
38}
39
40func registerMiddlewares(router *gin.Engine) {
41 middleware := NewMiddleware()
42
43 // Return 500 on panic
44 router.Use(gin.Recovery())
45 router.Use(gzip.Gzip(gzip.DefaultCompression))
46
47 // Response as JSON every c.Error in requests handler
48 router.Use(middleware.Errors)
49
50 // Handle OPTIONS and set default headers like CORS and Content-Type
51 router.Use(middleware.Options)
52 router.NoRoute(middleware.NotFound)
53 router.NoMethod(middleware.MethodNotAllowed)
54
55 // register a middleware to get all JWT auth
56 authMiddleware := jwt.NewJwtMiddleware(
57 config.App.JWTExp, []byte(config.App.JWTSecret))
58 router.Use(authMiddleware.Middleware)
59}
The schema is a class 2 that’s work like a wrapper to the actual graphql package.
Record that i have two diferent types for queries and mutations. And later, you’ll understand why.
Internal packages
In this package I’ll defined the scoped packages to this project. In the config package, are all our global 3 variables that we’ll use in the other packages.
Logs
You already noted that I really like to use packages 😆. So, here comes another one to you. Remember when we called the config.Setup in our main? So, here it’s.
In this function, we setup our loggers (by the way, I’m using the logrus package).
1func Setup() {
2 logrus.SetFormatter(&logrus.JSONFormatter{PrettyPrint: App.LogPrettyPrint})
3 level, err := logrus.ParseLevel(App.LogLevel)
4
5 if err != nil {
6 logrus.WithError(err).Fatal("parsing log level")
7 }
8
9 logrus.SetLevel(level)
10
11 logrus.WithFields(logrus.Fields{
12 "AppConfig": App,
13 "DatabaseConfig": Database,
14 }).Info("Environment variables")
15
16}
Why use logrus?
- Logrus cames with a bunch of tools that make our life easier, like change the log level (sending all levels bellow to /dev/null) hiding the logs below.
- Print variables/errors easily
- Chaining functions
- and anothers features
Utils and models
Again, I think that you have some knowledge about golang before reading this article, of course, you can learn it before, and make some minor tests. Take a look at https://goplay.tools/.
Basically, those are just packages that define some object/struct and some generic function that aren’t truly correlated to the controllers.
However, i think that’s necessary that you understand this:
When you saw something like:
1json:"field,omitempty" db:"field" binding:"omitempty,min=3"
It means that the package will be marshalized/unmarshilzed basing on this “field” key and, it can not to be in the object. And, when using the sqlx pkg, it’ll use the field as “:field” in the sql/internal marshaller.
The binding are actually, a validator. In this case, it won’t allow a field with 3 or less characters, however, due to omitempty, it allows you to don’t send this field and, therefore, won’t throw an error validation in this case.
Repositories and database connections
The repository package was build in this way to allow you an easy way to mock this repository for tests (I do recommend the testify library).
Services/jwt
Since we won’t have control of the request due to the handler wrapper, I’ll use a middleware that will register all headers in the request context for each received request.
And, when we need to get the token we just validate if there’s one in the content.
Controllers
I won’t explain detailed each one of the files here, since they are very simple to understand. Let’s move on.
helpers
AND HERE IT IS. THE MAIN PACKAGE. THE SOUL OF THIS PROJECT LoL.
In this package i define the main subpackages used by the other functions.
validators
In the bind.go I convert the graphql map into structs, and validate its fields.
1package validators
2
3import (
4 "github.com/ppcamp/go-graphql-with-auth/internal/utils"
5 "github.com/sirupsen/logrus"
6)
7
8func ShouldBind(dict map[string]interface{}, obj interface{}) error {
9 err := utils.Bind(dict, obj)
10 if err != nil {
11 logrus.WithError(err).Fatal("It shouldn't throw an error")
12 }
13
14 return Validator.Struct(obj)
15}
Which is really nice, since after convert, we don’t need to worry about miswrite some field or need to validate every little field manually or mapping it manually. I think you already note the power here right?

Do you remember that we are using the gin? The function above it was built basing on gin.Context.ShouldBind.
1package validators
2
3import (
4 "reflect"
5 "strings"
6
7 "github.com/gin-gonic/gin/binding"
8 "github.com/go-playground/validator/v10"
9)
10
11var Validator *validator.Validate
12
13func init() {
14 setupValidator()
15}
16
17func setupValidator() {
18 // if Validator == nil {
19 // Validator = validator.New()
20 // Validator.SetTagName("binding")
21 if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
22 Validator = v
23 Validator.RegisterTagNameFunc(func(fld reflect.StructField) string {
24 name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
25 if name == "-" {
26 return ""
27 }
28 return name
29 })
30
31 Validator.RegisterValidation("birthdate", birthdateValidation)
32 }
33}
Furthermore, we can just get the gin validator instance and register our new validation function and, even more, we can register a new TagNameFunc, which, if you need in the future, it can be used to get the json field name instead of the struct field name.
1package validators
2
3import (
4 "fmt"
5
6 "github.com/go-playground/validator/v10"
7)
8
9func MapErrorsToMessages(err error) (errors []string) {
10 for _, err := range err.(validator.ValidationErrors) {
11 errors = append(
12 errors,
13 fmt.Sprintf("Field %v failed validation for %v", err.Field(), err.Tag()))
14 }
15 return
16}
Just for example, I’ve create a birthdateValidation and register it into validator.
1package validators
2
3import (
4 "time"
5
6 "github.com/go-playground/validator/v10"
7 "github.com/ppcamp/go-graphql-with-auth/internal/utils"
8)
9
10var birthdateValidation validator.Func = func(fl validator.FieldLevel) bool {
11 birthdate, ok := fl.Field().Interface().(time.Time)
12 if ok {
13 return utils.IsAValidBirthDate(birthdate)
14 }
15 return false
16}
17
18// How to use?
19// type Some struct {
20// Field time.Time `json:"field,omitempty" binding:"birthdateValidation"`
21// }
controllers
In this package I define a decorator pattern inside a handler. This give me more control and flexibility. It’s like inserting a middleware between the actual handler and the function itself.
Basically, in the request_base.go, request_transaction.go and response.go, I define types and its equivalent interfaces (which will give) us the possibility of mocking those objects without needing to create a mocking server and registering all endpoints all over again. Awesome right?

Besides, using this approach we can create and manage the transaction basing on the response (commiting or don’t). Furthermore, we encapsulate the jwt getter to our middleware and we do the validations and parsing.
To be more clear, I’ll post in bellow the whole flux of getting an user query.
1// endpoints.go
2
3userController := user.NewUserControllerBuilder(storage)
4
5// a public query (without token)
6schema.RegisterQuery("users", userController.QueryUsers())
1// handlers.go
2
3// [QUERY] user
4func (t *UserControllerBuilder) QueryUsers() *graphql.Field {
5 return &graphql.Field{
6 Type: graphql.NewList(userType),
7 Description: "Get all users",
8
9 Args: graphql.FieldConfigArgument{
10 "nick": &graphql.ArgumentConfig{
11 Type: graphql.String,
12 },
13 "email": &graphql.ArgumentConfig{
14 Type: graphql.String,
15 },
16 "id": &graphql.ArgumentConfig{
17 Type: graphql.Int,
18 },
19 "skip": &graphql.ArgumentConfig{
20 Type: graphql.Int,
21 },
22 "take": &graphql.ArgumentConfig{
23 Type: graphql.Int,
24 },
25 },
26
27 Resolve: func(p graphql.ResolveParams) (interface{}, error) {
28 return t.handler.Request(p, &usermodels.UserQueryPayload{}, NewQueryUserController())
29 },
30 }
31}
Note the call to our defined controller decorator. This will parse the args and send them to the actual handler. And, basing on the type of NewQueryUserController, will create (or don’t), the transaction.
1// query.go
2
3type QueryUserController struct {
4 controller.TransactionControllerImpl
5}
6
7func (c *QueryUserController) Execute(pl interface{}) (result controller.ResponseController) {
8 result = controller.NewResponseController()
9 filter := pl.(*usermodels.UserQueryPayload)
10
11 users, err := c.Transaction.FindUsers(filter)
12 result.SetError(err)
13 result.SetResponse(users)
14 return
15}
16
17func NewQueryUserController() controller.TransactionController {
18 return &QueryUserController{}
19}
Note that by now, the filter it’s already parsed and valid and, if we return some error in the result, the transacion won’t be commited.
I bet that you liked, ‘cause I did 😎.
finally, the graphql package
You should be asking: WHY YOU DID A PACKAGE JUST FOR WRAPPING THE GRAPHQL-GO?. It’s just keep the more simplier that it can be.
We starting by defining some internal fields and our schema manager.
1package graphql
2
3import (
4 "log"
5
6 "github.com/gin-gonic/gin"
7 "github.com/graphql-go/graphql"
8 "github.com/graphql-go/handler"
9 "github.com/ppcamp/go-graphql-with-auth/internal/helpers/controller"
10)
11
12type Schema struct {
13 queries graphql.Fields
14 mutations graphql.Fields
15
16 authQueries graphql.Fields
17 authMutations graphql.Fields
18}
19
20func NewSchemaManager() *Schema {
21 return &Schema{
22 queries: graphql.Fields{},
23 mutations: graphql.Fields{},
24 authQueries: graphql.Fields{},
25 authMutations: graphql.Fields{},
26 }
27}
28
29func (s *Schema) RegisterQuery(fieldName string, fieldValue *graphql.Field) {
30 s.queries[fieldName] = fieldValue
31}
32
33func (s *Schema) RegisterMutation(fieldName string, fieldValue *graphql.Field) {
34 s.mutations[fieldName] = fieldValue
35}
36
37func (s *Schema) RegisterAuthenticatedQuery(fieldName string, fieldValue *graphql.Field) {
38 s.authQueries[fieldName] = fieldValue
39}
40
41func (s *Schema) RegisterAuthenticatedMutation(fieldName string, fieldValue *graphql.Field) {
42 s.authMutations[fieldName] = fieldValue
43}
Basically, in the graphql-go we don’t have a way to increase this queries when they already has been added to the object 4.
By creating a simple object that will be the father and, consequently the root object and, changing it’s resolver to a single one that will only validate if the token is valid or don’t, we can write something like this:
1func (s *Schema) registerAuthQueries() {
2 if len(s.authQueries) > 0 {
3 var me = graphql.NewObject(
4 graphql.ObjectConfig{
5 Name: "MeQuery",
6 Description: "Type to encapsulate all authenticated queries",
7 Fields: s.authQueries,
8 },
9 )
10
11 s.queries["me"] = &graphql.Field{
12 Type: me,
13 Description: "Run some query with jwt auth",
14 Resolve: controller.AuthorizedOnly,
15 }
16 }
17}
18
19func (s *Schema) registerAuthMutations() {
20 if len(s.authMutations) > 0 {
21 var me = graphql.NewObject(
22 graphql.ObjectConfig{
23 Name: "MeMutation",
24 Description: "Type to encapsulate all authenticated queries",
25 Fields: s.authMutations,
26 },
27 )
28
29 s.mutations["me"] = &graphql.Field{
30 Type: me,
31 Description: "Run some mutation with jwt auth",
32 Resolve: controller.AuthorizedOnly,
33 }
34 }
35}
Where the controller.AuthorizedOnly is defined in our beautifull helper.
1package controller
2
3import (
4 "github.com/graphql-go/graphql"
5 "github.com/ppcamp/go-graphql-with-auth/internal/services/jwt"
6)
7
8func AuthorizedOnly(p graphql.ResolveParams) (interface{}, error) {
9 _, err := jwt.GetSession(p.Context)
10 if err != nil {
11 return nil, err
12 } else {
13 return graphql.Field{}, err
14 }
15}
FINALLY we need to register our handlers and pass our gin Context into the graphql-go (graphql.ResolveParams)
1func (s *Schema) getSchemas() graphql.Schema {
2 s.registerAuthQueries()
3 s.registerAuthMutations()
4
5 // Schema
6 schema, err := graphql.NewSchema(graphql.SchemaConfig{
7 Query: graphql.NewObject(graphql.ObjectConfig{
8 Name: "Query",
9 Description: "All elements that can be fetched",
10 Fields: s.queries,
11 }),
12 Mutation: graphql.NewObject(graphql.ObjectConfig{
13 Name: "Mutation",
14 Description: "All functions that make some change in API",
15 Fields: s.mutations,
16 }),
17 })
18
19 if err != nil {
20 log.Fatalf("failed to create new schema, error: %v", err)
21 }
22
23 return schema
24}
25
26// Handler is a closure that will wrap the schema and return a proper gin
27// handler
28func (s *Schema) Handler() gin.HandlerFunc {
29 schema := s.getSchemas()
30
31 h := handler.New(&handler.Config{
32 Schema: &schema,
33 Pretty: true,
34 GraphiQL: true,
35 // Playground: true,
36 })
37
38 return func(c *gin.Context) {
39 h.ContextHandler(c, c.Writer, c.Request)
40 }
41}
42
Conclusions
In the end, you need to put in the balance if is it really necessary to implement the graphql approach. In my cause, there’s only one case that I should have used instead of RESTfull API until now.
I didn’t finished the project yet and I don’t know if I’ll, but let me told you what needs to be done:
- Add the migration into the startup routine
- refactor the controllers module
- create new type/resolve/repository for books (a user has books)
- create unitary tests.
- a good approach should be limit the depth of the query search, otherwise, the frontend user may create a huge query, which can, sometimes, broke the server.
If I made some mistake, or if you have some suggestion, ping me in the discussion below or send me a message.
The full project can be found here.
Best regards, @ppcamp