Machine generated documentation. Contribute to improve quality together.

Metadata-Driven Routing & Automatic Handler Registration

Routing in Go web applications can quickly become a tangled web of mux.HandleFunc(...) calls, especially as your API grows. Keeping route paths, handler names, and HTTP methods in sync across documentation and implementation can become a serious source of bugs and drift.

gohandlers eliminates this problem by embracing a metadata-driven approach to routing—automatically generating handler registrations based on your code’s structure and field tags. In this article, we’ll walk through how it works, how to use it, and how it keeps your server and docs perfectly in sync.

🎯 The Problem with Manual Routing

Here’s what typical manual routing might look like:

mux := http.NewServeMux()
mux.HandleFunc("/pets", listPetsHandler)
mux.HandleFunc("/pets/{id}", getPetHandler)
mux.HandleFunc("/pets", createPetHandler) // different method!

Issues this introduces:

✅ The gohandlers Solution

gohandlers generates routing metadata automatically by inspecting:

From this, it generates:

  1. A function that returns all your handlers as a map
  2. A YAML file documenting your HTTP surface
  3. Everything in sync with your real code

🔍 How Metadata is Collected

gohandlers parses your code and builds a model for each handler, including:

This metadata comes from:

Source What it provides
Function names Base name of the handler
Comments (// GET /users) HTTP method and path if explicitly declared
Tags on struct fields Parameters used in URL paths or query
Struct naming (XRequest) Used to infer method and path

If no method or path is declared, gohandlers uses naming conventions and your binding struct’s route: tags to derive them automatically.

🗂️ The ListHandlers() Function

For each group of handlers (e.g., all methods on a struct like *Pets), gohandlers generates a ListHandlers() method:

func (p *Pets) ListHandlers() map[string]HandlerInfo {
  return map[string]HandlerInfo{
  "CreatePet": {
    Method: "POST",
    Path:   "/pets",
    Ref:  p.CreatePet,
  },
  "GetPet": {
    Method: "GET",
    Path:   "/pets/{id}",
    Ref:  p.GetPet,
  },
  }
}

Each HandlerInfo struct includes:

You can now wire up all routes in one consistent loop:

mux := http.NewServeMux()
for _, h := range myHandler.ListHandlers() {
  mux.HandleFunc(h.Path, h.Ref)
}

🧠 Automatic Routing Benefits

📄 YAML Documentation Export

Along with code generation, gohandlers creates a YAML representation of your route metadata:

CreatePet:
  method: POST
  path: /pets
GetPet:
  method: GET
  path: /pets/{id}

This file is generated by the yaml command (triggered alongside list) and can be used to:

✍️ Declaring Metadata with Comments

If you want more control, gohandlers supports in-code comments above your handlers:

// POST /pets
// gh:list
func (p *Pets) CreatePet(...) { ... }

This explicitly tells gohandlers:

This is useful when the default inference from naming isn’t sufficient or if you want to override it.

⚙️ Filtering Handlers by Receiver

If your application is organized by service types (e.g., Pets, Users, Orders), you can generate handler lists per receiver using the --recv flag:

gohandlers list --dir handlers/pets --recv Pets --out list.gh.go

This lets you cleanly group and register handlers on a per-module basis.

🧼 Best Practices

🚀 Summary

gohandlers’ metadata-driven routing and automatic registration eliminates the need to manually wire up routes. It:

Whether your API has 5 endpoints or 500, gohandlers ensures every route is consistent, discoverable, and connected to real code.

One source of truth. Zero boilerplate. Fully synchronized.

Happy routing! 🗺️