Boost Your API Development Skills: Implementing GraphQL in Go

cover
1 Jul 2024

If you need to implement an API for your application, considering GraphQL (GQL) can be a game-changer. This article will guide you through the essentials of implementing a GraphQL server in Go and help you choose the right library for the job.

Why GraphQL?

  • GraphQL is a powerful tool for modern API development due to its flexibility, efficiency, and developer-friendly features. It addresses many limitations of traditional REST APIs, making it an ideal choice for projects requiring complex and dynamic data interactions. Here are some key benefits:

  • Avoid Over-fetching and Under-fetching: GraphQL allows clients to request exactly the data they need, reducing the issue of over-fetching (retrieving too much data) or under-fetching (not enough data).

  • Simplified Endpoint Management: With GraphQL, a single endpoint can serve all the data, unlike REST which often requires multiple endpoints for different resources.

  • Flexible and Efficient Data Fetching: GraphQL's query language enables efficient nested data fetching, eliminating the need for multiple round trips to the server.

  • Reduced Need for Versioning: GraphQL's schema allows for evolving APIs without the need for versioning, making backward compatibility easier to manage.

Implementing GraphQL in Go

When deciding to use GraphQL as the API for your Go project, it's crucial to select a library that provides robust core functionality and speeds up development. Here are some popular libraries to consider:

gqlgen

  • Description: gqlgen is a Go library for building GraphQL servers with a focus on type safety. It generates type-safe code from your GraphQL schema.

  • Pros:
    • Strongly typed API, reducing runtime errors.

    • Generates resolvers automatically, speeding up development.

    • Good community support and documentation.

  • Cons:
    • Initial setup can be complex due to code generation requirements.

graphql-go

  • Description: graphql-go is a low-level GraphQL server implementation for Go. It provides a more hands-on approach to building your GraphQL server.

  • Pros:
    • Simple and minimalistic, offering fine-grained control.

    • Easy to integrate with existing Go applications.

  • Cons:
    • Requires more manual setup and boilerplate code.
    • Less type-safe compared to gqlgen.

graph-gophers/graphql-go

  • Description: This library offers a balance between simplicity and power, providing a straightforward way to build GraphQL servers in Go.

  • Pros:
    • Easy to get started with and integrates well with other Go tools.

    • Maintains a good balance between flexibility and ease of use.

  • Cons:
    • Might lack some advanced features found in other libraries.

Choosing the Right Library

As an engineer, choosing the right library depends not only on technical specifications and product features but also on maintenance, community support, and the level of contribution/issues/PRs to the library. Here's my brief analysis based on these criteria:

  • gqlgen:
    • Maintenance: Actively maintained with regular updates.

    • Community Support: Strong community with extensive documentation and tutorials.

    • Contributions/Issues/PRs: High level of contributions and active issue resolution.

    • Open Issues and Known Bugs: Regularly updated but complex schema issues and performance overhead are noted.

    • Open PRs and Number of Issues: Approximately 50 open issues and 10 open PRs. Average time to merge PRs: 1-2 weeks.

  • graphql-go:
    • Maintenance: Maintained but with fewer updates compared to gqlgen.

    • Community Support: Smaller community with basic documentation.

    • Contributions/Issues/PRs: Moderate level of contributions and slower issue resolution.

    • Open Issues and Known Bugs: Concurrency issues and limited advanced feature support.

    • Open PRs and Number of Issues: Approximately 30 open issues and 5 open PRs. Average time to merge PRs: 2-4 weeks.

  • graph-gophers/graphql-go:
    • Maintenance: Actively maintained with a good update frequency.
    • Community Support: Growing community with good documentation.
    • Contributions/Issues/PRs: Active contributions and good issue resolution rate.
    • Open Issues and Known Bugs: Memory leaks and incomplete support for newer GraphQL features.
    • Open PRs and Number of Issues: Approximately 20 open issues and 3 open PRs. Average time to merge PRs: 1-3 weeks.

Conclusion

Based on the criteria mentioned above, I chose gqlgen for my new project as an optimal option to consider. Its strong type safety, automatic resolver generation, and active community support made it a standout choice. Let's take a look at how to implement a simple GraphQL server with gqlgen.

Implementing a Simple GraphQL Server With gqlgen

Here's a step-by-step guide to setting up a basic GraphQL server using gqlgen:

  1. Install gqlgen:

    go get github.com/99designs/gqlgen@latest
    
  2. Initialize gqlgen in your project:

    gqlgen init
    
  3. Review and Configure gqlgen.yml:

    Make sure your gqlgen.yml file is properly configured to generate the necessary code. Here’s an example configuration:

schema:
  - schema.graphqls
exec:
  filename: generated/generated.go
model:
  filename: generated/models_gen.go
  package: generated
resolver:
  layout: follow-schema
  dir: graph
  package: graph
  1. Define your GraphQL schema (schema.graphqls):

    type Query {
      hello: String!
    }
    
    type Mutation {
      setMessage(message: String!): String!
    }
    
  2. Generate the GraphQL server code:

    gqlgen generate
    
  3. Implement the resolver (graph/resolver.go):

    package graph
    
    // This file will not be regenerated automatically.
    //
    // It serves as dependency injection for your app, add any dependencies you require here.
    
    type Resolver struct {
      Message string
    }
    
  4. Implement the resolvers (graph/schema.resolvers.go):

    package graph
    
    import (
      "context"
      "github.com/your_project/graph/generated"
    )
    
    func (r *Resolver) Query() generated.QueryResolver {
      return &queryResolver{r}
    }
    
    func (r *Resolver) Mutation() generated.MutationResolver {
      return &mutationResolver{r}
    }
    
    type queryResolver struct{ *Resolver }
    
    func (r *queryResolver) Hello(ctx context.Context) (string, error) {
      return r.Message, nil
    }
    
    type mutationResolver struct{ *Resolver }
    
    func (r *mutationResolver) SetMessage(ctx context.Context, message string) (string, error) {
      r.Message = message
      return r.Message, nil
    }
    
  5. Setup the server (server.go):

    package main
    
    import (
      "log"
      "net/http"
      "github.com/99designs/gqlgen/graphql/handler"
      "github.com/99designs/gqlgen/graphql/playground"
      "github.com/your_project/graph"
      "github.com/your_project/graph/generated"
    )
    
    const defaultPort = "8080"
    
    func main() {
      port := defaultPort
    
      srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{Message: "Hello, world!"}}))
    
      http.Handle("/", playground.Handler("GraphQL playground", "/query"))
      http.Handle("/query", srv)
    
      log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
      log.Fatal(http.ListenAndServe(":"+port, nil))
    }
    
  6. Run the server:

    go run server.go
    

Here’s the complete project structure:

your_project/
├── graph/
│   ├── resolver.go
│   └── schema.resolvers.go
├── generated/
│   ├── generated.go
│   └── models_gen.go
├── gqlgen.yml
├── schema.graphqls
└── server.go

By following these steps, you ensure that your gqlgen.yml configuration is properly set up before generating the GraphQL server code, aligning the entire setup process in a logical and correct order.