feat: add composable middleware pipeline (Roadmap 3.2)#282
Conversation
New package: pkg/middleware/ - Chain() composes multiple middlewares into a single middleware - Middlewares applied in order: first argument is outermost layer - Supports composing chains (chain of chains) Template changes (both multi and single kits): - main.go.tmpl uses middleware.Chain() instead of nested function calls - Before: handler := rl(headers(recovery(logging(mux)))) - After: handler := middleware.Chain(rl, headers, recovery, logging)(mux) - Easier to reorder, extend, and compose separate chains for API/web groups 4 tests: composition order, empty chain, single middleware, chain composition. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR Review: feat: add composable middleware pipeline (Roadmap 3.2)The 🔴 Critical: Generated apps import
|
There was a problem hiding this comment.
Pull request overview
Adds a small reusable middleware composition helper (middleware.Chain) and updates generated kit templates to use it, making middleware pipelines easier to read and reorder in generated apps.
Changes:
- Introduce
pkg/middlewarewith aChain()helper for composinghttp.Handlermiddleware. - Add unit tests covering composition order, empty chain behavior, and chain-of-chains composition.
- Update multi/single kit
main.go.tmpltemplates to build the middleware stack viamiddleware.Chain(...)instead of nested calls.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| pkg/middleware/middleware.go | New Chain() helper for composing middleware with “first arg = outermost” semantics. |
| pkg/middleware/middleware_test.go | Tests for ordering and composition behavior of the chain helper. |
| internal/kits/system/single/templates/app/main.go.tmpl | Switch generated app middleware wrapping to middleware.Chain(...) and import new package. |
| internal/kits/system/multi/templates/app/main.go.tmpl | Same template update for multi kit, including rate limiter as outermost middleware. |
| "[[.ModuleName]]/app/home" | ||
| "[[.ModuleName]]/database" | ||
|
|
||
| "github.com/livetemplate/lvt/pkg/middleware" | ||
| "golang.org/x/time/rate" |
There was a problem hiding this comment.
The template now imports github.com/livetemplate/lvt/pkg/middleware, which pulls the github.com/livetemplate/lvt module into generated apps. That module currently declares go 1.26.0 (repo root go.mod), while this kit’s generated go.mod declares go 1.23. Users running Go <1.26 will hit module github.com/livetemplate/lvt requires go >= 1.26 during go mod tidy/build.
To keep lvt new --kit multi apps working with the advertised Go version, either (a) vendor/generate the small Chain helper into the app (e.g., internal/middleware or inline in main), or (b) update the kit’s go.mod.tmpl Go version/deps to match the required minimum.
| "[[.ModuleName]]/app/home" | ||
| "[[.ModuleName]]/database" | ||
|
|
||
| "github.com/livetemplate/lvt/pkg/middleware" | ||
| ) |
There was a problem hiding this comment.
This new import of github.com/livetemplate/lvt/pkg/middleware makes the base single-kit generated app depend on the github.com/livetemplate/lvt module. That module currently requires Go 1.26.0 (repo root go.mod), but this kit’s generated go.mod declares go 1.23, so go mod tidy/build will fail on Go <1.26.
Consider generating the Chain helper into the app instead of importing it from lvt, or bump the kit’s go.mod.tmpl Go version/dependencies accordingly.
The generated main.go was importing github.com/livetemplate/lvt/pkg/middleware, which made the CLI tool itself a runtime dependency of generated apps. This is a layering violation — lvt is a code generation tool, not a runtime library. Generated apps should be self-contained. Fix: Replace the import with an inline chainMiddleware() function emitted directly in the generated main.go template. The pkg/middleware package remains available as a standalone library for advanced use. Addresses review feedback from @claude[bot] and @copilot. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code Review – feat: add composable middleware pipeline (Roadmap 3.2)Overall this is a clean, well-motivated change. The readability improvement is real and the test coverage is solid. A few issues worth addressing before merge: Issues1. Duplication:
|
The generated main.go templates use an inline chainMiddleware() function. The separate pkg/middleware/ package duplicated this logic with no consumer. Remove the package to eliminate the disconnect flagged in review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code ReviewThe core change is a solid readability improvement — the flat Blocking Issues1. PR description doesn't match the diff The summary says:
But no such package appears in the diff or in
The test plan also references tests ( 2. Duplicated implementation
Minor ObservationsArgument order is non-obvious for callers chainMiddleware(http.DefaultServeMux, globalRL, securityHeadersMiddleware, ...)
// ^ handler first ^ middlewares afterMost middleware chain APIs (alice, chi's // Common pattern
handler := chainMiddleware(globalRL, securityHeadersMiddleware, recoveryMiddleware, loggingMiddleware)(http.DefaultServeMux)Not a bug, but worth a conscious decision since generated Comment on ordering semantics The comment SummaryThe readability improvement is real and the implementation is correct. The main issue is the mismatch between the PR description (which describes a |
Summary
Adds composable middleware pipeline to generated apps (Roadmap 3.2).
Generated main.go templates now use an inline chainMiddleware() helper instead of deeply nested function calls. The helper is ~7 lines, inlined directly in the generated main.go — no external package dependency.
Before: handler := globalRL(securityHeaders(recovery(logging(mux))))
After: handler := chainMiddleware(http.DefaultServeMux, globalRL, securityHeaders, recovery, logging)
Both multi and single kit templates updated.
Test plan