Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
06 Oct 2025 · 6 min read ·Article 98 / 125
Go

98 Building Your Own Plugin for graphql-go

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

98 Building Your Own Plugin for graphql-go

Building modern applications that are both scalable and maintainable is inseparable from choosing the right API architecture. GraphQL, with all of its advantages over REST, has become a favorite in today’s development ecosystem. One of the most popular Go libraries for implementing GraphQL is graphql-go . But did you know you can extend its capabilities by building your own plugins?

In this article, I’ll walk through the practice of creating custom plugins for the graphql-go library. Beyond theory, I’ll also provide code examples, flow diagrams, and a simulation of how to use the plugins you build yourself. This is important if you want to integrate cross-cutting application logic such as logging, validation, or even authentication in a modular way.


Why Do You Need Plugins in graphql-go?

Although graphql-go itself doesn’t ship with an official plugin system like the ones in TypeScript or Python, in practice we can build middleware, extensions, or even inject hooks into the GraphQL request flow. This pattern is commonly referred to as “plugin-like behavior” or “extension points”.

Example use cases:

NeedWhy a Plugin?
Query loggingEasier auditing, tracing, and debugging
Custom validationValidation outside the GraphQL schema (e.g., business validation)
Rate LimitingProtecting endpoints from abuse
Custom AuthenticationIntegration with SSO, JWT, tokens, etc.

Concept: Where Can Plugins Be Placed?

To understand this, we need to grasp the lifecycle of request handling in graphql-go. Put simply:

  1. The HTTP handler receives the GraphQL request.
  2. The handler decodes and parses the query.
  3. The query is executed against the root resolver.
  4. The result is encoded to JSON and sent back to the client.

We can inject extensibility most flexibly at the HTTP handler or at each resolver. Here is the request lifecycle diagram with the “hook” areas for plugins:

MERMAID
flowchart DK
    A[HTTP Handler Menerima Request] --> B{Plugin Middleware}
    B --> C[Decode / Parse Query]
    C --> D{Plugin Eksekusi Pre-Resolver}
    D --> E[Eksekusi Resolver]
    E --> F{Plugin Eksekusi Post-Resolver}
    F --> G[Encode Response]
    G --> H[Return ke Client]

Plugins can be installed in the handler middleware, before/after a resolver, or even inside the resolver itself.


Case Study: Building a GraphQL Query Logging Plugin

Let’s start with a simple use case: logging every query and its execution result.

Step 1: Project Setup

Create a new Go project and install graphql-go:

sh
1go mod init my-graphql-plugin
2go get github.com/graph-gophers/graphql-go
3go get github.com/graph-gophers/graphql-go/relay

Prepare a simple schema:

graphql
1# schema.graphql
2type Query {
3    hello(name: String!): String!
4}

Prepare a basic resolver:

go
1// resolver.go
2package main
3
4type Resolver struct{}
5
6func (r *Resolver) Hello(args struct{ Name string }) string {
7    return "Hello, " + args.Name
8}

Step 2: Building the Logging Plugin

Create a plugin struct that will later wrap the GraphQL handler from relay.

Plugin Interface (Middleware Pattern)

We define a plugin as a function that takes an http.Handler and returns another http.Handler:

go
1// plugin.go
2package main
3
4import "net/http"
5
6type Plugin func(http.Handler) http.Handler

Implementing the Logging Plugin

go
 1// logging_plugin.go
 2package main
 3
 4import (
 5    "bytes"
 6    "io"
 7    "io/ioutil"
 8    "log"
 9    "net/http"
10)
11
12func LoggingPlugin(next http.Handler) http.Handler {
13    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
14        if r.Method == http.MethodPost {
15            // Read the body (may be io.ReadCloser)
16            var buf bytes.Buffer
17            tee := io.TeeReader(r.Body, &buf)
18            body, _ := ioutil.ReadAll(tee)
19            r.Body.Close()
20            r.Body = io.NopCloser(&buf)
21            // Log the GraphQL query
22            log.Printf("[GraphQL-QUERY]: %s", string(body))
23        }
24        // Execute the next handler (relay)
25        next.ServeHTTP(w, r)
26    })
27}

Wrapping the Relay Handler With the Plugin

go
 1// main.go
 2package main
 3
 4import (
 5    "log"
 6    "net/http"
 7    "os"
 8    "github.com/graph-gophers/graphql-go"
 9    "github.com/graph-gophers/graphql-go/relay"
10)
11
12func main() {
13    schemaBytes, err := os.ReadFile("schema.graphql")
14    if err != nil {
15        log.Fatal(err)
16    }
17    schema := graphql.MustParseSchema(string(schemaBytes), &Resolver{})
18
19    relayHandler := &relay.Handler{Schema: schema}
20    // Wrap the handler with the plugin
21    handlerWithPlugin := LoggingPlugin(relayHandler)
22
23    http.Handle("/graphql", handlerWithPlugin)
24    log.Println("Server run at :8080")
25    log.Fatal(http.ListenAndServe(":8080", nil))
26}

Now, every incoming GraphQL request will have its log recorded:

bash
1$ curl -XPOST -d '{"query":"{ hello(name:\"Budi\") }"}' localhost:8080/graphql

The log on the server:

text
1[GraphQL-QUERY]: {"query":"{ hello(name:\"Budi\") }"}

Step 3: Supporting Multiple Plugins at Once

What if you want to chain several plugins together, for example logging, rate limiting, and authentication?

Define a function for chaining plugins:

go
 1// plugin_chain.go
 2package main
 3
 4import "net/http"
 5
 6func ChainPlugins(handler http.Handler, plugins ...Plugin) http.Handler {
 7    for _, plugin := range plugins {
 8        handler = plugin(handler)
 9    }
10    return handler
11}

How to use it:

go
1handlerWithPlugins := ChainPlugins(relayHandler,
2    LoggingPlugin,
3    RateLimitPlugin,
4    AuthPlugin,
5)
6http.Handle("/graphql", handlerWithPlugins)

Step 4: Simulation: Building a Simple Rate Limiting Plugin

As an illustration, here is an example of a simple plugin that limits requests to 5 per IP per minute:

go
 1// rate_limit_plugin.go
 2package main
 3
 4import (
 5    "net/http"
 6    "sync"
 7    "time"
 8)
 9
10type rateLimiter struct {
11    mu    sync.Mutex
12    store map[string][]time.Time
13}
14
15func NewRateLimiter() *rateLimiter {
16    return &rateLimiter{
17        store: make(map[string][]time.Time),
18    }
19}
20
21func (rl *rateLimiter) Middleware(next http.Handler) http.Handler {
22    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
23        ip := r.RemoteAddr
24        rl.mu.Lock()
25        reqs := rl.store[ip]
26        now := time.Now()
27        // Remove expired timestamps (older than 1 min)
28        newReqs := []time.Time{}
29        for _, t := range reqs {
30            if now.Sub(t) < time.Minute {
31                newReqs = append(newReqs, t)
32            }
33        }
34        if len(newReqs) >= 5 {
35            rl.mu.Unlock()
36            http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
37            return
38        }
39        rl.store[ip] = append(newReqs, now)
40        rl.mu.Unlock()
41
42        next.ServeHTTP(w, r)
43    })
44}

Combine it into ChainPlugins:

go
1limiter := NewRateLimiter()
2handlerWithPlugins := ChainPlugins(relayHandler,
3    LoggingPlugin,
4    limiter.Middleware,
5)

Step 5: Advanced—Per-Resolver Plugins

For access to a deeper context, you can build a wrapper around a resolver, for example to log the arguments of each resolver. Use a decorator interface, although this pattern is more advanced and verbose. Another option: inject dependencies into the resolver struct.


Conclusion

With the plugin-like middleware pattern in graphql-go, you can enrich your Go GraphQL server with modern middleware features in the style of ExpressJS or FastAPI—even though Go itself doesn’t have a native plugin syntax. By applying this pattern, logging, authentication, and even rate limiting can be managed in a modular, testable, and scalable way.

Going a step further, you can build your own open source plugins, distribute them to your team, or even share them with the community!


Summary

  • Plugins in Go = the middleware pattern, applied to HTTP handlers.
  • You can chain several plugins together.
  • Used for: logging, auth, monitoring, rate limiting, and so on.
  • Easy to integrate into GraphQL-go based projects.
  • Helps keep the codebase clean and modular.

Don’t hesitate to experiment with your own custom middleware—because a resilient architecture is one that is easy to extend! 🚀

Related Articles

💬 Comments