Hey r/golang. I've been building a full-stack Go web framework called Velocity. It's at v0.33, public on GitHub, and I'm looking for honest feedback from people who actually ship Go web apps.
The pitch: one framework with auth, ORM, queues, cache, mail, storage, broadcasting, scheduling, WebSockets, gRPC, and an Inertia adapter behind a unified API. ~30 packages, one config story.
The differentiator: env-var driver swapping
Same handler runs SQLite in dev and Postgres in prod. Same handler runs an in-memory queue in tests and Redis in prod. Change an env var, no code changes.
// Same handler. SQLite locally, Postgres in production.
func ListPosts(c *router.Context) error {
var posts []models.Post
if err := orm.NewQuery[models.Post]().
Where("published_at IS NOT NULL").
OrderBy("published_at", "desc").
Limit(20).
Find(c.Ctx(), &posts); err != nil {
return err
}
return c.JSON(200, posts)
}
# .env (dev)
DB_DRIVER=sqlite
DB_DSN=storage/dev.db
# .env.production
DB_DRIVER=postgres
DB_DSN=postgres://app:secret@db:5432/app
The same env-var-driven driver swap exists for cache (Redis / in-memory), queue (Redis / in-memory), and mail (SMTP / log-only).
What's in the box (concrete list)
- Routing. Radix-tree router, parameter / wildcard / regex segments, group-scoped middleware, status-aware response writer.
- ORM. Written from scratch, not a wrapper. Generic typed queries (
orm.NewQuery[User]()), associations, eager loading, scopes, soft deletes, dirty tracking on instances, migrations across SQLite / Postgres / MySQL. - Auth. Session and JWT guards side-by-side, policy-based authorization, login throttling, bcrypt password hashing, CSRF protection bound to session ID.
- Cache. Tagged invalidation, distributed locks with context propagation, atomic counters, per-driver pipelining.
- Queues. Delayed jobs, batched dispatch, retry policies with custom backoff, worker pool with bounded shutdown.
- Mail. SMTP and SaaS mailers, structured templates, attachment caps, CRLF rejection in headers.
- Storage. Local-filesystem and S3-compatible drivers behind one interface.
- Inertia adapter (
bondpackage). Native Inertia.js protocol implementation in Go, not an HTTP-bridge to a Node sidecar. The starter ships React + TypeScript + Tailwind + Vite with HMR on both backend and frontend. - CLI (
./vel). Code generation for every primitive, migrations, queue and scheduler workers, production build to a single binary. - Testing primitives. In-process test client (
testing/http.NewTestClient), genericfactory.NewModelFactory[T]with gofakeit-backed fake data, file-upload helpers, auth-in-tests via the auth manager directly (no fake form posts).
Secure by default, configurable
CORS rejects all origins until you allowlist (the dev-only opt-out is named InsecureAllowAllCORS()). Session and CSRF cookies default to Secure / HttpOnly / SameSite=Lax. JWT tokens signed with none are rejected at parse. Session IDs regenerate on privilege change. c.RealIP() honors a configured trust list, not whatever header the closest proxy attached.
Each default is overridable. The opt-outs are named so reviewers in a PR can spot them.
Pre-1.0 honesty
Currently v0.33. Things you should know before considering it for production:
- API moves between minor versions. Recent example: v0.31 → v0.32 changed the cache-lock signature to take a
context.Context. The CHANGELOG calls out every break with the migration path. - The ORM relations API is being polished.
- Benchmark numbers are not yet pinned.
- v1.0 will ship as
v1.0.0-rc.1first with a public testing window, thenv1.0.0. Once promoted, the commitment is to stay on v1 indefinitely (no v2 module path, additive evolution only).
If you need a v1-stable contract today, this is not the right pick yet. If you can adopt pre-1.0 and tolerate the occasional rename, you have an outsized voice in what v1.0 ends up being.
Why I'm posting
I'm the sole maintainer and I have a day job. Velocity is what I build on evenings and weekends. The framework is at the point where it covers a real Go web app's surface, but the API is still moving in places, and the right time to influence what v1.0 looks like is now.
If you're a Go dev who has assembled the same web stack three different ways and felt the glue-code grind, I'd love to hear what you'd want a framework like this to do (or explicitly not do).
Quickstart
brew install --cask velocitykode/tap/velocity
velocity new myapp && cd myapp && ./vel serve
# http://localhost:4000
- Tutorial: https://vel.build/blog/your-first-velocity-app
- Long-form intro: https://vel.build/blog/hello-velocity
- Repo: https://github.com/velocitykode/velocity
- Site: https://vel.build
Honest critiques welcome. "This is the wrong abstraction" feedback welcome. The repo is small enough that opinions still move the design.