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.
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
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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. 🔧💻
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
