Web API¶
HTTP handling and routing.
Request¶
Request wrapper with helper methods.
// Path params
id := c.Param("id")
idInt := c.ParamInt("id")
idUint := c.ParamUint("id")
// Query params
q := c.Query("q")
qDefault := c.DefaultQuery("page", "1")
// JSON body
var data MyStruct
c.BindJSON(&data)
// Get JSON values
name, _ := c.GetJsonStringValue("name")
// Headers
auth := c.GetHeader("Authorization")
// Pagination
page, _ := c.Page()
// Cookie
cookie := c.Cookie()
val := cookie.Get("session")
// Context
ctx := c.Ctx()
// Gin context
ginCtx := c.GinContext()
// Client IP
ip := c.ClientIP()
Response Types¶
// JSON (default)
return map[string]any{"key": "value"}, nil
// String
return "text", nil
// File download
return &web.FileResponse{Path: "/path/file.pdf", FileName: "doc.pdf"}, nil
// Redirect
return web.Redirect("/new-url"), nil
// Error
return nil, errors.New("error")
web.Message¶
web/message.go provides the standard response format used by the framework:
type Message struct {
Code int `json:"code"`
Data any `json:"data"`
Msg string `json:"msg"`
Type string `json:"type"`
}
Helper functions:
// Success responses
return web.Ok(), nil
return web.Ok("success"), nil
return web.Data(users), nil
return web.DataCode(http.StatusCreated, user), nil
return web.DataType("table", data), nil
// Check status
msg := web.Data(users)
if msg.IsOK() { /* code == 200 */ }
Usage in controllers:
func (c *UserController) List(req *web.Request) (any, error) {
users, err := c.userService.GetAll()
if err != nil {
return nil, err
}
return web.Data(users), nil
}
func (c *UserController) Create(req *web.Request) (any, error) {
if err := c.userService.Create(req); err != nil {
return nil, err
}
return web.Ok("created"), nil
}
ErrorCode¶
web/error_code.go defines structured error codes. Returning an *ErrorCode from a handler lets the converter map it to the correct HTTP status.
Error code constants¶
| Constant | Value | Meaning |
|---|---|---|
CodeOK |
200 | Success |
CodeBadRequest |
400 | Bad request |
CodeUnauthorized |
401 | Unauthorized |
CodeForbidden |
403 | Forbidden |
CodeNotFound |
404 | Not found |
CodeMethodNotAllowed |
405 | Method not allowed |
CodeTooManyRequests |
429 | Too many requests |
CodeInternalError |
500 | Internal server error |
CodeServiceUnavailable |
503 | Service unavailable |
CodeValidationFailed |
1001 | Validation failed |
CodeDuplicateEntry |
1002 | Duplicate entry |
CodeTokenExpired |
1003 | Token expired |
CodeTokenInvalid |
1004 | Token invalid |
Constructing errors¶
return nil, web.NewBadRequest().WithDetail("invalid params")
return nil, web.NewUnauthorized().WithError(err)
return nil, web.NewForbidden().WithDetail("access denied")
return nil, web.NewNotFound().WithDetail("user not found")
return nil, web.NewTooManyRequests().WithDetail("rate limited")
return nil, web.NewInternalError().WithError(err)
return nil, web.NewServiceUnavailable().WithDetail("try later")
// Business errors
return nil, web.NewValidationError().WithError(err)
return nil, web.NewDuplicateEntry().WithDetail("email exists")
return nil, web.NewTokenExpired().WithDetail("token expired")
return nil, web.NewTokenInvalid().WithDetail("token invalid")
// Custom code
return nil, web.NewErrorCode(2001, "custom error").WithDetail("...")
ErrorCode structure¶
type ErrorCode struct {
Code int `json:"code"`
Message string `json:"message"`
Detail string `json:"detail,omitempty"`
Err error `json:"-"`
}
Codeis serialized to the response JSON.Messageis a short summary.Detailis optional extra information.Errholds the wrapped error; it is not serialized but supportserrors.Is/errors.As.
HasMeta¶
Query route metadata (set via WithMeta) in filters or handlers:
// Check if route has a metadata key
if req.HasMeta(RequireAuth()) {
// route requires authentication
}
MetaOption¶
// Define
func RequireAuth() web.MetaOption {
return web.WithValue("require_auth", true)
}
// Use on route
builder.Get("/profile", handler).WithMeta(RequireAuth())
// Query in filter — unified with WithMeta
func (f *AuthFilter) Handle(fc web.FilterChain, req *web.Request) (any, error) {
if req.HasMeta(RequireAuth()) {
// verify token
}
return fc.Next()
}
Built-in constructors:
- web.WithKey(keys ...string) — match if any key exists
- web.WithValue(key string, value any) — match if key exists and value equals (via reflect.DeepEqual)
GetHandler (Testing)¶
WebFrame.GetHandler() initializes the full application (DB, services, routes) and returns an http.Handler for use with httptest.NewServer(). This is the recommended way to write integration tests.
func TestUserAPI(t *testing.T) {
cfg := config.NewConfig()
cfg.Put("server.port", "19009")
builder := wf.NewBuilder(cfg)
builder.Get("/users", func(req *web.Request) (any, error) {
return web.Data([]string{"alice", "bob"}), nil
})
app := builder.Build()
handler := app.GetHandler()
ts := httptest.NewServer(handler)
defer ts.Close()
// Set Host header to match the configured port
req, _ := http.NewRequest("GET", ts.URL+"/users", nil)
req.Host = "localhost:19009"
resp, _ := http.DefaultClient.Do(req)
// assert response...
}
The handler dispatches requests to the correct Server engine based on the port in the Host header. Each server's routes, filters, and ContextPath remain fully independent.
For simpler tests that don't need a real HTTP server, use app.Test() instead (see Core API).
Response Converter¶
web/converter.go converts handler return values into HTTP responses. The framework uses web.DefaultConverter by default. It already handles *web.Message, *web.ErrorCode, files, redirects, WebSocket, SSE, and more.
When writing a custom converter, embed *web.DefaultConverter and reuse its methods so you don't miss message, file, or error handling. See the full guide at Response Converter.
web.Converteronly hasRequest, butRestGroupBuilder.Converter()expectscore.IConverter, so you still need to implementInit(ctx *core.Context) errorwhen registering it.
Default conversion rules¶
| Handler return value | Response behavior |
|---|---|
*web.Message |
JSON response with Message.Code as HTTP status; redirects when Code == 301 |
*web.ErrorCode |
JSON error response via ClassifyError |
*web.FileResponse / *os.File |
File download |
*web.SSEResponse |
Open SSE stream |
*web.WSResponse |
Upgrade to WebSocket |
string |
Plain text |
| Any other value | Wrap as web.Data(value) and return JSON |
Simple example¶
type APIConverter struct {
core.IConverter
*web.DefaultConverter
}
func (c *APIConverter) Init(ctx *core.Context) error { return nil }
func (c *APIConverter) Request(fc web.FilterChain, req *web.Request) {
// Run the default conversion logic first
c.DefaultConverter.Request(fc, req)
// Add your own logic, e.g. logging or metrics
}