Buffalo — Architecture#
Architectural style#
Layered Framework Library (Ports-and-Adapters)
Buffalo is a web framework delivered as a pure Go library — there is no buffalo binary. User applications call buffalo.New(opts) in their own main.go to obtain an *App and then call app.Serve(). Internally, the App sits at the centre of a ports-and-adapters layout: render, servers, worker, binding, and plugins are independent subsystems (adapters) that App orchestrates. None of the sibling packages import each other; all coupling flows inward toward App.
Component diagram (textual)#
User application (main.go)
│
▼ buffalo.New(Options)
┌───────────────────────────────────────┐
│ App (root pkg) │
│ ┌──────────┐ ┌────────────────────┐ │
│ │ Options │ │ Home │ │
│ │ (config) │ │ (routing group: │ │
│ └──────────┘ │ Middleware, │ │
│ │ ErrorHandlers, │ │
│ │ gorilla/mux router│ │
│ └────────────────────┘ │
│ │
│ adapters (no cross-imports) │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ binding/ │ │ render/ │ │
│ │ (decode │ │ (HTML/JSON/XML/ │ │
│ │ bodies) │ │ SSE/MD/DL) │ │
│ └──────────┘ └──────────────────┘ │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ servers/ │ │ worker/ │ │
│ │ (HTTP/ │ │ (background jobs)│ │
│ │ TLS/ │ └──────────────────┘ │
│ │ Unix) │ │
│ └──────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ plugins/ (external binary IPC) │ │
│ └──────────────────────────────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ internal/ (env, httpx, meta, │ │
│ │ nulls, templates) │ │
│ └──────────────────────────────────┘ │
└───────────────────────────────────────┘
│ app.Serve()
▼
goroutine: servers.Server.Start(ctx, app)
goroutine: worker.Worker.Start(ctx)
goroutine: graceful shutdown watcherCore components#
App#
- Package:
github.com/gobuffalo/buffalo(app.go) - Responsibility: Central orchestrator; owns the HTTP lifecycle, routing, middleware, error handling, session management, workers, and event emission.
- Key types:
App(struct embeddingOptions+Home) - Dependencies:
Home,Options,servers,worker,plugins,gorilla/mux,gobuffalo/events
Home#
- Package:
github.com/gobuffalo/buffalo(home.go) - Responsibility: Routing group container; holds
MiddlewareStack,ErrorHandlers, the underlying*mux.Router, and static file paths. Being extracted fromAppas part of the road-to-v1 refactor to separate application lifecycle from routing concerns. - Key types:
Home(struct) - Dependencies:
gorilla/mux,MiddlewareStack
Context / DefaultContext#
- Package:
github.com/gobuffalo/buffalo(context.go, default_context.go) - Responsibility: Per-request state bag. Wraps
http.ResponseWriter,*http.Request, session, cookies, flash, params, logger, and template data. Passed to everyHandlerand middleware. - Key types:
Context(interface),DefaultContext(concrete struct) - Dependencies:
binding,render,gorilla/mux,gorilla/sessions
Handler / MiddlewareStack#
- Package:
github.com/gobuffalo/buffalo(handler.go, middleware.go) - Responsibility:
Handleris the fundamental unit of request processing (func(Context) error).MiddlewareStackmanages an ordered slice ofMiddlewareFuncwrappers with per-handler skip, remove, replace, and clone operations. - Key types:
Handler(type alias),MiddlewareFunc(type alias),MiddlewareStack(struct) - Dependencies: stdlib
reflect+runtimefor pointer-based function identity
render.Engine#
- Package:
github.com/gobuffalo/buffalo/render(render.go) - Responsibility: Unified rendering subsystem. Dispatches to format-specific
Rendererimplementations: HTML via plush templating, JSON, XML, plain text, Server-Sent Events, file download, markdown, JS. Configurable template helpers and custom template engines. - Key types:
Engine(struct),Renderer(interface),Options - Dependencies:
gobuffalo/plush,gobuffalo/helpers
servers.Server#
- Package:
github.com/gobuffalo/buffalo/servers(servers.go) - Responsibility: Abstracts the HTTP serving layer.
Simplewraps*http.Server,TLSadds certificate loading,Listenerwraps a pre-creatednet.Listener. ProvidesWrapXxxfactory functions for user customisation. - Key types:
Server(interface),Simple,TLS,Listener - Dependencies: stdlib
net/http,net
worker.Worker#
- Package:
github.com/gobuffalo/buffalo/worker(worker.go) - Responsibility: Background job queue abstraction. Defines an interface for
Perform,PerformAt,PerformIn,Register,Start,Stop. The built-inSimpleimplementation runs jobs in goroutines; third-party adapters (e.g. gocraft/work) implement the interface. - Key types:
Worker(interface),Handler(type alias),Job - Dependencies: stdlib
context,time
plugins#
- Package:
github.com/gobuffalo/buffalo/plugins(plugins.go) - Responsibility: Discovers and dispatches to CLI plugins installed as external binaries. On
LoadPlugins(), runsbuffalo-plugins availableas a subprocess and parses JSON output to learn what commands are available. Dispatches lifecycle events (e.g.buffalo build) to registered plugin binaries via JSON IPC. Entirely process-boundary coupling — no Go import of third-party plugins. - Key types:
Plugin,Plugins,Command - Dependencies: stdlib
os/exec,encoding/json,gobuffalo/events
Data flow#
A typical HTTP request flows as follows:
1. net/http → App.ServeHTTP(w, r)
2. → processPreHandlers (optional pre-middleware: http.Handler or PreWare chain)
3. → method override (MethodOverride handler rewrites POST _method field)
4. → gorilla/mux router matches path/method → RouteInfo
5. → MiddlewareStack.handler(info) wraps Handler with all middleware in reverse order
(assertMiddleware always innermost, then RequestLogger, PanicHandler, defaultErrorMiddleware,
then user-defined middleware)
6. → App.newContext(info, w, r) creates DefaultContext:
- merges mux path vars + query/form params
- attaches session, flash, logger, template data
7. → User Handler(ctx Context) error
- reads ctx.Params(), ctx.Bind() for input
- returns ctx.Render(200, render.JSON/HTML/...) or ctx.Error(code, err)
8. → render.Renderer writes Content-Type header + encoded body to response
9. ← Response flushed to net/httpBackground jobs are enqueued via app.Worker.Perform(job) from any handler and processed asynchronously by the worker goroutine started in Serve().
Initialization / Bootstrap#
buffalo.New(opts Options) *App:
LoadPlugins()— discovers external buffalo CLI plugins (subprocess call)env.Load()— loads.envfile via godotenv if presentoptionsWithDefaults(opts)— fills in GO_ENV, ADDR, PORT, HOST, SESSION_SECRET, LOG_LEVEL from env vars usingcmp.Or; creates default gorilla cookie session store, logger, and simple worker- Constructs
AppwithHome{router: mux.NewRouter(), ErrorHandlers: {...}}and a defaultMiddlewareStackcontainingRequestLogger,defaultErrorMiddleware,PanicHandler - Returns
*Appready for route registration
app.Serve(srvs ...servers.Server):
- Emits
EvtAppStartlifecycle event - Selects default server (TCP or UNIX socket based on
Addr) - Sets up
signal.NotifyContextfor SIGTERM/SIGINT - Launches goroutines via
sync.WaitGroup:- Shutdown watcher (listens for context cancellation → shuts down servers then worker)
- Worker goroutine (
worker.Start(ctx)) unlessWorkerOff - One goroutine per
servers.Servercallingserver.Start(ctx, app)— passesappashttp.Handler
wg.Wait()blocks until all goroutines return
There is no dependency injection framework. All wiring is manual via the Options struct. Consumers pass implementations (custom servers.Server, worker.Worker, sessions.Store) directly into Options fields.
Configuration#
Buffalo is configured entirely through the Options struct passed to buffalo.New(). Defaults are populated from environment variables in optionsWithDefaults():
| Option | Env var | Default |
|---|---|---|
Env | GO_ENV | "development" |
Addr | ADDR, PORT | 127.0.0.1:3000 (dev) / 0.0.0.0:3000 (prod) |
Host | HOST | http://127.0.0.1:3000 |
LogLvl | LOG_LEVEL | DebugLevel |
SessionStore | SESSION_SECRET | gorilla cookie store |
SessionName | — | "_buffalo_session" |
TimeoutSecondShutdown | — | 60 |
No Viper, no config files, no etcd. Configuration is intentionally minimal: env vars for deployment, direct struct fields for programmatic control. Uses Go 1.21’s cmp.Or for precedence-ordered defaults.
Key design decisions#
Library over framework binary. Buffalo provides zero executables. Users own
main.go, which means the framework never controls the program entry point. The trade-off: easier to embed and customise; harder to provide scaffolding and live-reload without a separate CLI tool (buffaloCLI, which is a separate repo).Handler func(Context) erroroverhttp.Handler. The single-method function type is far simpler than the stdlib interface and enables middleware written as higher-order functions wrappingHandler. Error return values propagate cleanly to centralized error handlers rather than requiringhttp.Errorcalls scattered across handlers.Middleware skip-by-handler-identity using reflection pointers. The
MiddlewareStackidentifies functions by their runtime pointer address (viareflect.ValueOf(f).Pointer()), allowing per-handler middleware skipping without any decorator types or build-time annotation. This is a pragmatic but fragile approach that breaks with closures and anonymous functions.Process-boundary plugin architecture. Third-party tools integrate by installing an executable named
buffalo-*on$PATH. Buffalo discovers capabilities via JSON over stdout IPC, not Go plugin imports or shared objects. This avoids Go’s plugin ABI fragility and version coupling entirely, at the cost of requiring external binaries.Homestruct extraction (in-flight, road-to-v1). The routing group concerns (Middleware,ErrorHandlers,router,filepaths) are actively being separated from application lifecycle concerns (Serve,Stop,Worker,Logger) into aHomestruct. This refactor makes explicit what was implicit: anApp.Group()is a routing scope, not a full application, and should not exposeServe()/Stop(). Code shows the transition is incomplete —Appstill has bridging fields (root,appSelf,children) and TODOs.