4 GraphQL Server Architectures in Go
4 GraphQL Server Architectures in Go
GraphQL has become the new standard for building scalable and flexible APIs. The Go (Golang) ecosystem itself, while often associated with REST, actually already provides plenty of tools and architectural options for building a production-ready GraphQL server.
In this article, I’ll walk through four GraphQL server architectures commonly used in Go, complete with code examples, simulations, and flow diagrams using Mermaid to make the visualizations easier to grasp.
1. Monolithic GraphQL Server
Description
The monolithic architecture is the simplest pattern—all of the business logic, resolvers, and data access live in a single codebase/service. In Go, popular libraries such as graph-gophers/graphql-go or 99designs/gqlgen are typically used.
When to use it:
- Small to medium-scale projects
- Small teams
- MVPs or prototyping
Advantages:
- Easy to develop and deploy
- Low overhead
- Simple debugging
Drawbacks:
- Hard to scale
- Tight coupling between resolvers and data
Simple code example (using gqlgen):
1// go.mod
2module monolith-graphql
3
4go 1.18
5
6require (
7 github.com/99designs/gqlgen v0.17.24
8)1# schema.graphqls
2type Query {
3 hello: String!
4}1// resolver.go
2package graph
3
4type Resolver struct{}
5
6func (r *Resolver) Query_hello() string {
7 return "Hello from Monolithic GraphQL!"
8} 1// main.go
2package main
3
4import (
5 "log"
6 "net/http"
7 "monolith-graphql/graph"
8 "github.com/99designs/gqlgen/graphql/handler"
9 "github.com/99designs/gqlgen/graphql/playground"
10)
11
12func main() {
13 srv := handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: &graph.Resolver{}}))
14 http.Handle("/", playground.Handler("GraphQL playground", "/query"))
15 http.Handle("/query", srv)
16 log.Fatal(http.ListenAndServe(":8080", nil))
17}Flow Diagram
graph TD;
User-->GraphQLServer
GraphQLServer-->Resolver
Resolver-->Database
2. Schema Stitching
Description
Once your API starts growing and spans many domains (user, product, payment), you can split the schema and its logic into several modules, then combine them using schema stitching. In Go, graphql-go-tools from WunderGraph or gqlmerge can be used to perform the stitching.
When to use it:
- Large teams working on different domains
- A codebase you want to keep modular
- Scaling at the team/organization level
Advantages:
- Easy to maintain per domain
- Schema modules can be reused
Drawbacks:
- Resolver registration is more complicated
- Versions and dependencies must be carefully controlled
Code example (simulating the modular pattern):
1graph
2├── user/
3│ ├── schema.graphqls
4│ └── resolver.go
5├── product/
6│ ├── schema.graphqls
7│ └── resolver.go
8├── main.go1# user/schema.graphqls
2type Query {
3 user(id: ID!): User
4}
5type User { id: ID!, name: String! }1# product/schema.graphqls
2type Query {
3 product(id: ID!): Product
4}
5type Product { id: ID!, name: String! } 1// main.go (fragment)
2import (
3 // ...
4 "github.com/wundergraph/graphql-go-tools/pkg/executor"
5 userSchema "yourapp/user/schema.graphqls"
6 productSchema "yourapp/product/schema.graphqls"
7)
8
9func main() {
10 mergedSchema := mergeSchemas(userSchema, productSchema)
11 exec := executor.NewExecutor(mergedSchema)
12 // Bind to HTTP handler
13}Flow Diagram
graph TB;
User-->UserService
Product-->ProductService
UserService-->SchemaStitchingLayer
ProductService-->SchemaStitchingLayer
SchemaStitchingLayer-->Client
3. Federation (Apollo Federation Inspired)
Description
Federation is an architecture in which each domain/service runs its own GraphQL server with a composable schema. There is then a single Gateway (Apollo Gateway, or your own implementation in Go) that merges the schemas and becomes the main entrypoint for clients.
In Go, you can use gqlgen-federation .
When to use it:
- Microservices
- Per-domain ownership
- Multi-team/organization setups
Advantages:
- Scalability
- Independence per team/domain
- You can deploy a service without taking the gateway down
Drawbacks:
- More complex (infra & devops)
- Consistency and dependencies between services
- Latency increases when the service chain is long
Simulated Code Example:
1// product/schema.graphqls
2type Product @key(fields: "id") {
3 id: ID!
4 name: String!
5}
6extend type Query {
7 product(id: ID!): Product
8}1// user/schema.graphqls
2type User @key(fields: "id") {
3 id: ID!
4 name: String!
5 purchasedProducts: [Product]
6}
7extend type Query {
8 user(id: ID!): User
9}Apollo Gateway).Flow Diagram
graph TD;
Client-->GraphQLGateway
GraphQLGateway-->UserGraphQLService
GraphQLGateway-->ProductGraphQLService
4. BFF (Backend for Frontend) Pattern
Description
BFF is a pattern in which a single GraphQL server is designed specifically for the needs of a particular frontend/app (for example: mobile vs. web). In Go, GraphQL is often used as a data aggregation layer pulling from various backends (REST, RPC, DB, and so on).
When to use it:
- A single frontend with unique requirements
- You want to aggregate multiple backends
- Optimizing the query/response for one specific consumer
Advantages:
- A highly tailored API for one frontend
- You can optimize security and caching
- Field usage tracking is easier to monitor
Drawbacks:
- Not reusable across frontends
- Schema must be synced whenever a new frontend is added
Code Example:
We’ll aggregate data from a REST API and a database, then expose it via GraphQL:
1// resolver.go (pseudo code)
2func (r *Resolver) Query_user(ctx context.Context, id string) (*User, error) {
3 // Fetch from REST
4 resp, _ := http.Get(fmt.Sprintf("https://api.mycompany.com/users/%s", id))
5 var userData User
6 json.NewDecoder(resp.Body).Decode(&userData)
7
8 // Fetch from DB (e.g., list of orders)
9 orders := db.GetOrdersForUser(id)
10 return &User{...userData, Orders: orders}, nil
11}
1// resolver.go (pseudo code)
2func (r *Resolver) Query_user(ctx context.Context, id string) (*User, error) {
3 // Fetch from REST
4 resp, _ := http.Get(fmt.Sprintf("https://api.mycompany.com/users/%s", id))
5 var userData User
6 json.NewDecoder(resp.Body).Decode(&userData)
7
8 // Fetch from DB (e.g., list of orders)
9 orders := db.GetOrdersForUser(id)
10 return &User{...userData, Orders: orders}, nil
11}Comparison Simulation (Table)
| Pattern | Topology | When to Use | Scalability | Easiest to Build? | Suitability |
|---|---|---|---|---|---|
| Monolith | Single service | Small project, MVP | Low | Yes | Tight team, low overhead |
| Schema Stitching | Modular, 1 process | Medium-large, domain spec | Medium | Medium | Several domains, team |
| Federation | Microservices + Gateway | Org-wide, multi-product | High | No | Large org, microservices |
| BFF | 1:1 for each frontend | Per app/frontend | Medium | Medium | Tailored, per app |
Conclusion
Building a GraphQL server in Go is highly flexible—you can start from a monolith, then refactor toward a modular, federation, or BFF approach depending on your business needs and team growth.
Rule of thumb:
- Start simple and evolve toward more complex patterns as scale and requirements grow.
- Go is more than capable for production GraphQL—choose the library and architecture that fit your project’s phase and your team’s expertise.
References
- https://gqlgen.com
- https://github.com/99designs/gqlgen
- https://wundergraph.com/docs/graphql/golang
- https://www.apollographql.com/docs/federation/
Suggestions? Feedback? Experiences migrating GraphQL architectures in Go? Feel free to share them in the comments!
Happy coding! 🚀