FW is a lightweight Go web framework for building APIs quickly with annotation-style routing, middleware binding, dependency injection, and automatic OpenAPI generation.
- Annotation-based routes such as
@GET /users - Auto-registration for controllers, services, and middlewares
- Global, controller-level, and method-level middleware
- Automatic request binding for path, query, header, and body
- Unified JSON response envelope
- OpenAPI generation with Swagger UI
cmd/fwCLI for scaffolding and builds- Optional config hot reload, applied per top-level section
- Generic controllers and methods, with real OpenAPI schemas inferred from type arguments
Add FW to your project:
go get github.com/linxlib/fw/v2Install the CLI if needed:
go install github.com/linxlib/fw/v2/cmd/fw@latestRequirements: Go 1.27 or later. Starting with v2.1.0 the module declares
go 1.27 in go.mod, so older toolchains refuse to build it (go.mod requires go >= 1.27).
Go 1.27 is needed to parse Go 1.27 method generics.
v2.1.0 keeps the module path github.com/linxlib/fw/v2, so upgrading is a plain
go get with no import rewrite — but it is not source-compatible if you use the
config package directly.
| v2.0.1 | v2.1.0 |
|---|---|
config.Load(path string) (Config, error) |
config.Load(target any) error |
config.Default() |
removed — use app.DefaultEngineConfig() |
config.ServerConfig / LogConfig / RecoveryConfig / OpenAPIConfig |
moved to the app package |
Config.Server / .Log / .OpenAPI / .Recovery / .ProjectDir / .Middlewares |
removed — Config is now a loader built with config.New(&config.Option{...}) |
Section struct with GetString / GetInt / GetBool / GetStrings / GetDuration / Sub / MustGet / UnmarshalYAML |
Section is map[string]any; only Get and Has remain |
// v2.0.1
cfg, err := config.Load("config/app.yaml")
host := cfg.Server.Host
ttl := mw.Config.GetDurationDefault("timeout", 5*time.Second)
// v2.1.0
type opt struct {
Server struct {
Host string `inject:"host" default:"\"0.0.0.0\""`
Port int `inject:"port" default:"8080"`
} `inject:"server"`
}
c := config.New(&config.Option{Files: []string{"config/app.yaml"}})
var o opt
_ = c.LoadWithKey("server", &o) // or c.Load(&o) / c.LoadByTags(&o)
// middleware config: Section is a plain map, assert values yourself
if mw.Config.Has("timeout") {
d, _ := time.ParseDuration(mw.Config.Get("timeout"))
}If you only call app.New("config/app.yaml"), no code change is needed.
RouteInfo, MediaType and Schema gained fields (ResponseExample, Example,
Default). Positional composite literals without field names no longer compile — add
field names.
- Environment variables are derived differently. Old: from the
yamltag (FW_LOG_FILE_PATH,FW_RECOVERY_RETURN_STACK_TO_BODY). New: from theinjectkey plus the Go field name (FW_LOG_FILEPATH,FW_RECOVERY_RETURNSTACKTOBODY).FW_SERVER_HOST,FW_SERVER_PORT,FW_LOG_LEVELandFW_OPENAPI_ENABLEDare unchanged.FW_MIDDLEWARES_*no longer works — middleware config is YAML-only. - A missing config file no longer fails startup; defaults are used instead. Only
.yamlfiles are accepted. - Defaults changed:
log.file_pathlogs/fw.log→"",recovery.return_stack_to_bodytrue→false,openapi.titleFW API→fw API. - The same middleware bound at several levels now runs once, keeping the most specific binding (method > controller > global). Deduplication is by name, so two different implementations sharing a name means the less specific one never runs.
- Collection routes lose the trailing slash:
@Route /users+@GET /now maps to/users. Clients hard-coding/users/get 404;/usersanswers directly instead of 301-redirecting. - Regenerate
.astp.json(go generate ./...) if you deploy with embedded AST metadata; v2.0.1 files miss generic base-class routes.
- Missing query/header parameters fall back to the zero value instead of returning 500.
- Same-type parameters no longer leak each other's values (
?page=2&size=5). :namepath parameters are actually reachable — on v2.0.1 they silently 404'd because fasthttp/router only understands{name}.
Create a new project:
fw init demo
cd demo
fw build
go run .Typical startup:
package main
import (
_ "embed"
"github.com/linxlib/fw/v2/app"
)
//go:generate go run github.com/linxlib/fw/v2/astp/cmd/astp -exported .
//go:embed .astp.json
var astpData []byte
func main() {
e, err := app.New("config/app.yaml")
if err != nil {
panic(err)
}
e.EmbedProject(astpData)
if err := e.ListenAndServe(); err != nil {
panic(err)
}
}Simple controller:
package controllers
import (
"github.com/linxlib/fw/v2/context"
"github.com/valyala/fasthttp"
)
// UserController handles user APIs.
// @Controller
// @Route /api/v1/users
type UserController struct{}
// Detail returns one user.
// @GET /:id
func (c *UserController) Detail(ctx context.Context, id string) error {
return ctx.Respond(fasthttp.StatusOK, 0, "ok", map[string]any{"id": id})
}For coding agents:
- App development guide: APP_AGENT.md
- Documentation maintenance guide: DOCS_AGENT.md
Useful commands:
fw init myapp
fw build
fw create controller --name User --route /api/v1/users
fw create service --name User
fw create middleware --name Authorization