HTTP Layer
Built on Echo. core.IHTTPContext is both a core.IContext (all capabilities) and an echo.Context (request/response).
Server
e := core.NewHTTPServer(app, &core.HTTPOptions{
AllowOrigins: []string{"https://app.example.com"},
})
e.POST("/users", controller.CreateUser)
core.StartHTTPServer(e, env) // blocking in dev; graceful shutdown otherwiseThe stack applies request-id, structured request logging, panic recovery (panic → 500, never a crash), and CORS. The error handler renders any core.IError as {code, message, fields} with the right status.
NewHTTPServer returns a *core.Server that remembers the App — route methods take the handler directly (no app threaded through every call). It embeds *echo.Echo, so all Echo methods (Use, Static, Pre, …) still work.
Route groups
e := core.NewHTTPServer(app, nil)
api := e.Group("/api", authMiddleware) // *core.Group, also carries the App
api.GET("/users", ListUsers)
api.POST("/users", CreateUser)
v2 := api.Group("/v2") // nested groups work too
v2.GET("/users/:id", GetUser)Route methods: GET, POST, PUT, PATCH, DELETE, each accepting a core.HandlerFunc plus optional echo.MiddlewareFunc.
Escape hatch: for raw Echo registration you can still use
e.Echo.POST(path, core.WithHTTPContext(app, h)).
Handlers
type HandlerFunc func(c core.IHTTPContext) error
func CreateUser(c core.IHTTPContext) error {
var req CreateUserRequest
if err := c.BindWithValidate(&req); err != nil {
return err
}
// c.DB(), c.Cache(), repository.New[T](c) — all ambient-context
return c.JSON(201, result)
}Binding
BindOnly / BindWithValidate bind every request source (like echo), using struct tags:
| Source | Tag | Example |
|---|---|---|
| Path params | param:"id" | /users/:id |
| Query params | query:"q" | ?q=foo (all methods, incl. GET) |
| JSON body | json:"name" | Content-Type: application/json |
| Form / multipart | form:"name" | application/x-www-form-urlencoded, multipart/form-data |
| XML body | xml:"name" | application/xml |
type UpdateUserReq struct {
ID string `param:"id" json:"-"`
Q string `query:"q"`
Name *string `json:"name" form:"name"`
}
func UpdateUser(c core.IHTTPContext) error {
var req UpdateUserReq
if err := c.BindWithValidate(&req); err != nil { // binds path+query+body, then Valid(ctx)
return err
}
// req.ID from :id, req.Q from ?q, req.Name from JSON/form body
...
}- Uploaded files:
c.FormFile("avatar")/c.MultipartForm()(echo methods). - Malformed JSON →
{code: "INVALID_JSON"}(400). - JSON type mismatch →
{code: "INVALID_PARAMS", fields: {<field>: INVALID_TYPE}}. - Bad path/query/form value →
{code: "INVALID_PARAMS"}(400). BindOnlybinds without validating;BindWithValidate= bind +req.Valid(ctx).
Pagination
opts := c.GetPageOptions() // limit/page/q/order_by
opts := c.GetPageOptionsWithAllowed("name", "created_at") // order-by allowlistorder_by=name asc,created_at desc parses to ["name asc", "created_at desc"]. With an allowlist, columns not in it are dropped (blocks SQL injection via order_by).
Returning errors
Return any core.IError from a handler and the framework renders it:
return errmsgs.NotFound
return core.New(400, "INVALID_STATE", "cannot transition")
return ctx.NewError(dbErr, errmsgs.DBError) // logs at 500, wraps cause