Flow is an opinionated, small, and developer-friendly MVC framework for Go. It provides a tiny routing DSL, controller/context helpers, a simple view manager with layouts/partials, cookie sessions, a migrations runner, and generators to scaffold controllers, models, views and migrations. Flow's design favors explicitness, testability and a pleasant developer loop — similar in spirit to Rails, but idiomatic Go.
This README gives a concise introduction, quickstart, and reference for the main building blocks so you (or contributors) can get started quickly.
New to Flow? Start with the Getting Started guide for a full walkthrough: scaffold → migrate → controller → serve.
- Small, dependency-free internal router with RESTful
Resourceshelper. - Public
pkg/flowAPI withController,Context,ViewManager, andAppbootstrap. - Simple template loading with layout and partial support (conventions:
views/{controller}/{action}.html,views/layouts/*.html,views/partials/*). - Cookie-based sessions & flash helpers (lightweight, no external deps).
- Migration runner (timestamped up/down SQL) and CLI generator scaffolding (controllers, models, migrations).
- A PoC Bun ORM adapter and
AutoMigratehelper; generator now emits Bun-tagged model structs and SQL migrations when fields are provided. - Basic ORM helpers and CRUD/transaction helpers exposed on
pkg/flow:Insert,Update,Delete,FindByPK,BeginTxandRunInTxto simplify transactional patterns and generated-model usage.
The repository contains a small example app under examples/simple. To run it from a Linux environment (or WSL on Windows):
# from the repository root. Replace <repo_root> with your repository path.
cd <repo_root>
go run ./examples/simpleOpen a browser or curl:
curl http://localhost:3000/users/1The example demonstrates controllers and views (see examples/simple/app/controllers and examples/simple/app/views).
There is also a Bun ORM demo that demonstrates wiring the Bun adapter into the App, running AutoMigrate, and doing basic DB operations (Linux/WSL):
# from the repository root. Replace <repo_root> with your repository path.
cd <repo_root>
go run ./examples/bun_demoSee docs/bun.md for more details on using generated Bun models and migrations.
If you want a quick compile/run check for generated models, see internal/generator/gen_compile_test.go — it demonstrates generating a model into a temporary project, compiling a small program that uses the generated Save/Delete methods and running it to ensure end-to-end compilation.
For a faster developer loop you can run the CLI in watch mode which restarts the server when source files change. The watcher lives in the CLI and spawns a child go run ./cmd/flow serve --no-watch process — the --no-watch flag is internal and prevents recursive watchers.
Basic usage (defaults watch current directory and common source files):
# run watcher and serve on :3000
flow serve --watch --addr :3000Customize what to watch and what triggers a restart:
--watch-paths— comma-separated list of directories to watch (default:.).--watch-ignore— comma-separated list of path names or simple patterns to ignore (default:.git,vendor,node_modules).--watch-ext— comma-separated list of file extensions that should trigger a restart (default:.go,.tmpl,.html,.sql). If empty, all file changes are considered.
Examples:
# watch only cmd and internal directories, ignore node_modules, and restart only on .go and .tmpl files
flow serve --watch --watch-paths cmd,internal --watch-ignore node_modules --watch-ext .go,.tmpl
# watch everything (no extension filter)
flow serve --watch --watch-ext ""Notes:
- The watcher debounces rapid file events to avoid repeated restarts.
- Default ignore patterns include
.git,vendorandnode_modulesto avoid noisy events. - Use
--watch-extto reduce noise and speed up the loop (recommended).
Flow includes several small, useful middleware constructors (logging, request id,
timeout and simple metrics). You can enable them when constructing an App
using the provided functional options. The WithDefaultMiddleware() option
registers a sensible stack (Recovery, RequestID, Logging, Metrics).
Example — enable the default middleware stack:
import (
"time"
"github.com/goflow-framework/flow/pkg/flow"
)
app := flow.New("my-app",
flow.WithAddr(":3000"),
flow.WithDefaultMiddleware(),
)
// start the app
_ = app.Start()Example — customize middleware (add a per-request timeout):
app := flow.New("my-app",
flow.WithAddr(":3000"),
flow.WithRequestID("X-Request-ID"),
flow.WithLogging(),
flow.WithTimeout(5*time.Second),
)To opt-out of particular middleware (for example to disable CSRF or rate limiting),
construct your App using the smaller building blocks (e.g. WithRequestID,
WithLogging, WithMetrics) or manually register the middleware you want via
app.Use(...). See docs/security.md for details on secure defaults and how to
incrementally opt in/out.
Practical recipes, security checklist, plugin guidance and generator usage live in the docs/ folder. Below are short summaries and pointers to the longer documents.
-
Security checklist
Use these items as a baseline for shipping secure apps with Flow:
- Enable
WithDefaultMiddleware()in production or explicitly callWithSecureDefaults()to opt-into stricter cookie and header defaults. Seedocs/security.mdfor the full rationale and examples. - Ensure TLS termination is configured and that HSTS is only enabled when your service is reachable via HTTPS (the
SecureHeadersmiddleware handles this by default). - Run CI gates:
gofmt,go vet,staticcheckand (optional)gosecto catch formatting, correctness, and common security issues early. - Keep secrets out of logs: use structured logging adapters that support redaction (see
pkg/flow/logger.go) and prefer theRedactMaphelper for structured data. - If deployed behind a reverse proxy or load balancer, configure
TrustedProxieson the rate limiter soX-Forwarded-Foris validated correctly. Seedocs/rate-limiting.mdfor the full security model and examples. - Set an explicit body size limit in production.
WithDefaultMiddlewareapplies a 4 MiB cap automatically; override withWithBodyLimitfor your use case. Seedocs/body-limit.mdfor per-route overrides and choosing the right limit.
- Enable
- To control the framework's built-in redaction behavior use the
WithRedactionApp option when constructing your App. For example:flow.New("app", flow.WithRedaction(false))to opt out of automatic redaction and manage it externally.
-
Plugin guide and lifecycle
Flow supports application-scoped plugins that follow a small lifecycle:
Init,Start,Stop. Seedocs/plugins.mdfor details. Recommended pattern:- Prefer runtime registration: call plugin constructors and
App.RegisterServicefrommain()or the CLIservecommand. This avoids package init-time side effects and makes dependencies explicit. - Implement plugin
Start(ctx context.Context) errorandStop(ctx context.Context) errormethods to participate in graceful shutdowns. The framework will callStartafter the HTTP server begins accepting requests andStopduring shutdown. - Keep plugins small and focused; prefer composition over one large plugin. Plugins should register any background workers via App helpers so the App can manage their lifecycle.
- Prefer runtime registration: call plugin constructors and
-
Generators and scaffolding
Flow provides generators to scaffold controllers, models and migrations. See
docs/generator.mdanddocs/admin_generator.mdfor full examples and the generator CLI flags. Tips:- Run generators from the project root. Generated code follows the
examples/*layout and is intended to be edited afterwards. - Generated models include Bun tags when requested; the
docs/bun.mddocument explains how to wire the Bun adapter and runAutoMigrate.
- Run generators from the project root. Generated code follows the
If you want to add a new recipe to the Cookbook, please add a short markdown doc under docs/ and update this README to point to it.
Make sure you have Go 1.20+ (project uses module mode). These commands assume a Linux environment — on Windows, run them inside WSL.
go get github.com/goflow-framework/flow@v0.10.0Then import the package in your Go code:
import "github.com/goflow-framework/flow/pkg/flow"The module is published at pkg.go.dev/github.com/goflow-framework/flow.
# run all tests (replace <repo_root> with your repository path)
cd <repo_root> && go test ./... -v
# build the project (replace <repo_root> with your repository path)
cd <repo_root> && go build ./...CI enforces gofmt formatting. To format the repository locally you can run the provided scripts:
- On WSL / Linux / macOS:
./scripts/format.sh- On Windows PowerShell (run from the repository root):
.\scripts\format.ps1
# add -Commit to automatically commit formatting changes
.\scripts\format.ps1 -CommitThe scripts run gofmt -w . (and goimports -w . if available) and print any remaining files that need formatting. After running, stage and commit the changes before pushing.
Developer commands (quick)
If you prefer running the commands directly instead of the scripts, here are common developer shortcuts you can use in WSL / Linux / macOS (run PowerShell variants on Windows as shown above).
# format all Go files in-place
gofmt -w .
# list files that would be reformatted (useful for CI validation)
gofmt -l .
# optionally run goimports if available to fix imports
goimports -w .
# run the full test suite (verbose)
go test ./... -v
# run tests for the public package only (faster during development)
go test ./pkg/flow -v
# run tests without cache (useful when editing test helpers)
go test ./... -count=1Below is a quick map of important packages and conventions to understand Flow's internals and how to use it.
-
internal/router— a small HTTP router used by the framework. Supports parameterized routes (/users/:id),Get/Post/...helpers andResources(base, controller)for RESTful wiring. -
pkg/flow— the public API surface used by application code:App(inpkg/flow/app.go): application bootstrap (router, middleware, server lifecycle, Views and Sessions).Context(inpkg/flow/context.go): request-scoped helper passed to controller actions (render, JSON, params, sessions, flash).Controller(inpkg/flow/controller.go): base type and adapter helpers.ViewManager(inpkg/flow/view.go): template loader, caching, layout and partial resolution.SessionManager(inpkg/flow/session.go): cookie-based sessions and flash helpers.
We added optional secure-default middleware to make it easy to harden apps. See
docs/security.mdfor details onSecureHeaders,SessionCookieHardening, and theWithSecureDefaultshelper. Example:examples/security_demodemonstrates enabling secure defaults on anApp.
Flow exposes a NewRouter(app *flow.App) constructor. Handlers accept func(*flow.Context) which simplifies controller code. Example:
app := flow.New("my-app")
r := flow.NewRouter(app)
// register a Context-based handler
r.Get("/hello", func(ctx *flow.Context) {
ctx.JSON(200, map[string]string{"hello": "world"})
})
// resources (RESTful routes)
users := NewUsersController(app) // implement flow.Resource
_ = r.Resources("users", users)
app.SetRouter(r.Handler())Flow exposes a small structured-concurrency helper RequestGroup accessible
from a request *flow.Context. See docs/requestgroup.md for usage
examples, cancellation semantics and a small demo app under
examples/requestgroup_demo.
The MakeResourceAdapter(app, res) adapts a flow.Resource (methods that accept *Context) to the internal router.
See also: Executor & background workers (docs/executor.md) for guidance on running background tasks and wiring job workers to the App.
Flow reuses request *flow.Context instances via an internal sync.Pool to reduce per-request
allocations. Pooling is enabled by default to improve throughput and reduce GC pressure. Key points:
flow.New(...)initializes a per-AppContextPoolwhichNewContextprefers when creating request contexts. This provides isolation between App instances while preserving a fast, zero-alloc reuse path.- A package-level fallback pool is used when an App-local pool is not available.
If you need to disable pooling globally (for debugging or to measure baseline allocations) set:
import "github.com/goflow-framework/flow/pkg/flow"
func init() {
flow.UseContextPool = false
}You can also toggle flow.UseContextPool in tests or benchmarks to compare pooled vs non-pooled
behaviour (see pkg/flow/context_bench_test.go for microbenchmarks included with this repo).
Flow uses html/template and a ViewManager with a small convention:
- Template directory: configurable via
NewViewManager("views"). - View lookup:
views/{controller}/{action}.html(useViewManager.Render("users/show", data, ctx)). - Layouts: put shared layouts in
views/layouts/*.html(layouts can call{{ template "content" . }}to insert the view content). - Partials: put reusable fragments in
views/partials/*.htmland reference them in templates.
Example controller rendering:
func (u *UsersController) Show(ctx *flow.Context) {
data := map[string]interface{}{"Title": "User", "ID": ctx.Param("id")}
_ = u.Render(ctx, "users/show", data)
}Flow includes a minimal cookie-based signed session manager with helpers attached to Context:
ctx.Session()returns the session for the request.ctx.AddFlash(kind, message)adds a flash message.ctx.Flashes()reads and clears flash messages.
The implementation is intentionally small and dependency-free to keep things portable and testable.
Accessing the session-stored user id
If you use the generated auth scaffolding, the middleware and controller helpers store the
authenticated user's id in the session. The generated middleware exposes a helper
GetSessionUserID that converts the stored session value into an int64 safely.
Example usage inside a handler (replace the middleware import path with your module path):
import (
middleware "github.com/your/module/path/app/middleware" // replace with your module
flow "github.com/goflow-framework/flow/pkg/flow"
)
func (c *SomeController) Show(ctx *flow.Context) {
if s := ctx.Session(); s != nil {
if id, ok := middleware.GetSessionUserID(s); ok {
// id is the authenticated user's ID (int64)
_ = id // use it (e.g. fetch full user from DB if needed)
}
}
// ...handler logic
}There is an internal migrations runner that expects timestamped *.up.sql and *.down.sql files and runs them in a transaction. The CLI scaffolding provides generator helpers to create controllers, models, views and migrations.
Check internal/migrations and internal/generator for the implementation and templates.
New generator features:
flow generate model NAME [fields...]— generate a model with optional field definitions (eg.title:string published_at:datetime). The generator will emit Bun struct tags (bun:"field_name") and a migration SQL with the specified columns.flow generate scaffold NAME [fields...]— generate controller, model, views and migrations; fields are forwarded to the model generator.- CLI:
cmd/flowupdated sogenerate modelandgenerate scaffoldaccept variadic field args. - Generated models now include small convenience methods (
Save(ctx, app)andDelete(ctx, app)) which call into theflowCRUD helpers. This makes generated code immediately usable with the Bun PoC adapter. - Generator integration tests: the repo contains CLI integration tests that build the CLI, run the generator into a temp project, and assert generated files and migration SQL. There's also a compile-and-run test that builds a tiny program against the generated model to ensure the generated code compiles and runs.
New generator flags:
--no-i18n— when provided to scaffold/admin/auth generators, the generator will skip creating the minimalapp/i18n/en.yamltranslation file. This is useful for projects that don't need built-in i18n scaffolding.
See docs/generator.md for detailed generator flag documentation, field syntax and examples.
The project is organized to be easy to contribute to:
- Write small, focused tests (packages already include tests for router, migrations, generator templates).
- Follow repository conventions: controllers under
examples/<app>/app/controllers, views underexamples/<app>/app/views. - Run
go test ./...before opening a PR.
Suggested issues to start with (examples):
- Add more
ViewManagerFuncMap helpers (url helpers, formatters). - Add flags and options to generators (force, db dialect).
- Improve CLI UX (cobra commands, descriptive messages).
The repository implements a working prototype with router, controllers, views (layouts/partials), sessions, migrations, and generators. Recent additions include:
- A Proof-of-Concept Bun ORM adapter (
internal/orm) andpkg/flowhelpers (WithBun,SetBun,App.Bun()andAutoMigrate). - Generator upgrades to accept field lists and emit Bun-tagged models and migration SQL.
- CLI generator commands accept field arguments (
flow generate model NAME [fields...],flow generate scaffold NAME [fields...]). - Documentation (
docs/bun.md) and a runnable example (examples/bun_demo) demonstrating Bun usage. - Basic ORM helper surface added to
pkg/flow:Insert,Update,Delete,FindByPK,BeginTxandRunInTxplus transaction helpers used by generated models. - Generator templates updated to include
SaveandDeletemodel methods so generated models are immediately usable. - Integration tests for generator CLI and a compile/run test ensure generated code compiles and behaves as expected.
Planned improvements:
- richer view helpers and FuncMap support,
- generator flags and options (eg.
--orm,--force), and more robust field parsing (defaults, indexes, constraints), - a
flow migrateCLI wrapper for applying migrations, - CI workflow and developer DX improvements (hot-reload integration),
- fuller documentation and more examples.
Flow exposes a minimal Executor abstraction for running background work and integrating job workers with the App lifecycle. Key points:
- The shared Executor interface lives in
pkg/execand is intentionally tiny:Submit(ctx, fn)andShutdown(ctx). - You can provide your own Executor to the App with
flow.WithExecutor(myExec); in this case the App will use it but will not manage its lifecycle (Start/Shutdown) unless you also provide an explicit shutdown function. - To have the App create and manage a bounded executor for you, use
flow.WithBoundedExecutor(n, queueSize)— the App will callShutdown(ctx)on that executor duringApp.Shutdown. - Use
App.StartWorker(queue, handlers, opts)to start apkg/jobWorkerthat processes aRedisQueue. If the App has an Executor configured the worker will submit job handler executions to that executor; otherwise handlers run synchronously in worker goroutines. - The App records workers started via
StartWorkerand will callStop()on them and wait for them to finish duringApp.Shutdownso background work is drained before process exit.
This design keeps the runtime small and explicit: provide an executor when you want centralized, bounded concurrency and let the App manage shutdown for executors it created.
This project is provided under an MIT-style license. Modify as appropriate for your needs.