Skip to content

HTTP Layer

Built on Echo. core.IHTTPContext is both a core.IContext (all capabilities) and an echo.Context (request/response).

Server

go
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 otherwise

The 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

go
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

go
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:

SourceTagExample
Path paramsparam:"id"/users/:id
Query paramsquery:"q"?q=foo (all methods, incl. GET)
JSON bodyjson:"name"Content-Type: application/json
Form / multipartform:"name"application/x-www-form-urlencoded, multipart/form-data
XML bodyxml:"name"application/xml
go
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).
  • BindOnly binds without validating; BindWithValidate = bind + req.Valid(ctx).

Pagination

go
opts := c.GetPageOptions()                          // limit/page/q/order_by
opts := c.GetPageOptionsWithAllowed("name", "created_at")  // order-by allowlist

order_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:

go
return errmsgs.NotFound
return core.New(400, "INVALID_STATE", "cannot transition")
return ctx.NewError(dbErr, errmsgs.DBError) // logs at 500, wraps cause

Maintained by Passakon Puttasuwan & Dev Core Team.