Zero-Code Routing in Gin: Auto-Register APIs, Proxy Data, and Generate Swagger from Config

This article demonstrates how to eliminate manual route definitions in a Go Gin service by using a JSON configuration to dynamically register endpoints, map parameters, rewrite response bodies, and automatically generate Swagger documentation, while also offering enhancements like validation, caching, and hot-reloading.

Code Wrench
Code Wrench
Code Wrench
Zero-Code Routing in Gin: Auto-Register APIs, Proxy Data, and Generate Swagger from Config

Motivation

Traditional Gin services require writing a handler, binding parameters, adding Swagger comments, and redeploying for each new endpoint. When the upstream tool aktools updates frequently and its parameters and return values are inconsistent, maintaining such code becomes repetitive.

Configuration‑Driven Architecture

All routing, parameter mapping, response field renaming, and Swagger generation are driven by a single config/config.json file. Modifying this file is sufficient to add, remove, or change an API without touching Go source code.

{
  "get_stock_info": {
    "url": "stock_individual_info_em",
    "description": "获取股票信息",
    "bodytype": "items",
    "params": { "symbol": "" },
    "bodys": {
      "最新": "current_price",
      "股票代码": "symbol",
      "股票简称": "name",
      "总股本": "total_share",
      "流通股": "float_share",
      "总市值": "total_market_value",
      "流通市值": "float_market_value",
      "行业": "industry",
      "上市时间": "listing_date"
    }
  }
}

Key implications:

API name becomes the URL path.

Parameters defined in params are remapped to the names expected by the upstream service.

Response fields are automatically renamed according to bodys.

Swagger JSON is generated from the configuration, eliminating manual annotations.

Dynamic Gin Route Registration

The main.go loop iterates over the configuration and registers a route for each entry:

for key, cfg := range utility.AkToolsCfg {
    router.GET("/"+key, func(c *gin.Context) {
        params := utility.ChangeParams(c.Request.URL.Query(), cfg.Params)
        data, err := utility.FetchData(cfg.Url, params)
        if err != nil {
            c.JSON(http.StatusBadGateway, utility.NewRespMessage("fetch error", err))
            return
        }
        body, _ := utility.ChangeBody(data, cfg.Bodys, cfg.BodyType)
        c.JSON(http.StatusOK, json.RawMessage(body))
    })
}

Each configuration entry automatically creates a Gin route, removing the need for manual router.GET() calls.

Parameter Remapping (ChangeParams)

func ChangeParams(values url.Values, aparams map[string]string) string {
    if len(values) == 0 {
        return values.Encode()
    }
    var params string
    for k, v := range values {
        if aparam, ok := aparams[k]; ok && aparam != "" {
            params += aparam + "=" + url.QueryEscape(v[0]) + "&"
            continue
        }
        params += k + "=" + url.QueryEscape(v[0]) + "&"
    }
    return params[:len(params)-1]
}

This function maps external query parameters to the names required by the upstream service.

Response Body Renaming (ChangeBody)

func ChangeBody(data []byte, abodys map[string]string, bodyType string) ([]byte, error) {
    if bodyType == "items" {
        var items []Item
        if err := json.Unmarshal(data, &items); err != nil {
            return data, err
        }
        rst := make(map[string]interface{}, len(items))
        for _, item := range items {
            if name, ok := abodys[item.Item]; ok && name != "" {
                rst[name] = item.Value
                continue
            }
            rst[item.Item] = item.Value
        }
        return json.Marshal(rst)
    }
    if bodyType == "array" {
        var items []map[string]interface{}
        if err := json.Unmarshal(data, &items); err != nil {
            return data, err
        }
        for _, item := range items {
            for aName, name := range abodys {
                if v, ok := item[aName]; ok && name != "" {
                    item[name] = v
                    delete(item, aName)
                }
            }
        }
        return json.Marshal(items)
    }
    return data, nil
}

The function rewrites response fields based on the configuration, supporting both list‑of‑items and array formats.

Automatic Swagger Generation

// Generate Swagger docs for each API
for key, config := range cfg {
    path := "/" + key
    doc["paths"].(map[string]interface{})[path] = map[string]interface{}{
        "get": map[string]interface{}{
            "summary": config.Description,
            "responses": map[string]interface{}{
                "200": map[string]interface{}{
                    "description": "成功",
                    "schema": buildResponseSchema(config),
                },
            },
        },
    }
    if params := buildParameters(config.Params); params != nil {
        doc["paths"].(map[string]interface{})[path].(map[string]interface{})["get"].(map[string]interface{})["parameters"] = params
    }
}

The generated Swagger JSON stays in sync with the configuration, removing the need for manual documentation.

Additional Enhancements

Parameter Validation

if rule.Required && c.Query(param) == "" {
    c.JSON(400, gin.H{"error": fmt.Sprintf("missing param %s", param)})
    c.Abort()
    return
}

Caching and Circuit Breaker

cacheKey := cfg.Url + "?" + params
if data, ok := localCache.Get(cacheKey); ok {
    return data, nil
}
for i := 0; i < 3; i++ {
    data, err := httpGet(cfg.Url, params)
    if err == nil {
        localCache.Set(cacheKey, data, time.Minute)
        return data, nil
    }
}
return nil, fmt.Errorf("fetch failed after 3 retries")

Hot Configuration & Swagger Reload

watcher.Add("config/config.json")
for event := range watcher.Events {
    if event.Op&fsnotify.Write == fsnotify.Write {
        ReloadConfig()
        GenerateSwagger()
    }
}

When the JSON file changes, routes and documentation refresh automatically without restarting the service.

Repository

GitHub: https://github.com/louis-xie-programmer/easy-stock-analyzer

Gitee: https://gitee.com/louis_xie/easy-stock-analyzer

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Godynamic routingSwagger
Code Wrench
Written by

Code Wrench

Focuses on code debugging, performance optimization, and real-world engineering, sharing efficient development tips and pitfall guides. We break down technical challenges in a down-to-earth style, helping you craft handy tools so every line of code becomes a problem‑solving weapon. 🔧💻

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.