The full-stack Go framework that doesn't get in the way of you or your agents.
Docs, component gallery, and live demos → · Quickstart · A GoFastr app in production
GoFastr is a pre-v1 full-stack Go framework. The API can still change between releases, under the deprecation policy. Declare your domain in Go and get server-rendered screens, REST endpoints, MCP tools, an OpenAPI spec, SQL migrations, and a typed query builder. The output is plain Go you can read, edit, and own: no reflection discovers your entities, no generated code you can't open. Auth, background jobs, search, and storage are opt-in packages, and you can drop to net/http or database/sql at any point.
It is built for both the agentic web and AI-assisted development. The app you ship joins the agentic web: the agents your users bring call your data over MCP, with the same login and permissions your users have. While you build, gofastr dev hands your coding agent, Claude Code or Codex, the app's routes, config, and logs over MCP, to help build and debug it.
Start with the quickstart. Or scaffold a whole app in one command: screens, API, and auth from gofastr init <name>, or gofastr generate from a one-file declaration (blueprint tutorial).
A shoutout to CodeRabbit: it reviews every
PR in this repo and keeps catching what everyone else missed, like six Major
findings on #198 while
the checks list showed pass. Every finding is triaged on the PR before merge.
Requires Go 1.27+. Install the CLI:
go install github.com/DonaldMurillo/gofastr/cmd/gofastr@latestThree complete programs, smallest to fullest, the same three the
site's homepage shows (examples/site): plain core/, one framework
entity, and the full app shape. CI extracts all three from this README,
compiles them, boots them, and curls them
(cmd/gofastr/readme_quickstart_test.go).
core/ is stdlib-first building blocks: router, typed handlers,
render, a SQL query builder, schema, migrate, mcp. Each is usable without
the framework. The basic app is one screen and one API route:
package main
import (
"context"
"net/http"
"github.com/DonaldMurillo/gofastr/core/handler"
"github.com/DonaldMurillo/gofastr/core/render"
"github.com/DonaldMurillo/gofastr/core/router"
)
type Pong struct {
Status string `json:"status"`
}
func main() {
r := router.New()
// A server-rendered page.
r.Get("/", render.HTMLHandler(func(req *http.Request) render.HTML {
return render.Tag("h1", nil, render.Text("Hello from core."))
}))
// A typed JSON route: the adapter binds input and serializes output.
r.Get("/api/ping", handler.HandlerAdapter(func(ctx context.Context, _ struct{}) (Pong, error) {
return Pong{Status: "ok"}, nil
}))
http.ListenAndServe(":8080", r)
}One framework entity is a complete server: a migrated table, REST
CRUD, an OpenAPI spec, and MCP tools. Add only what you need from there.
package main
import (
"database/sql"
"log"
"github.com/DonaldMurillo/gofastr/core/schema"
"github.com/DonaldMurillo/gofastr/framework"
_ "github.com/DonaldMurillo/gofastr/sqlite/stdlib"
)
func main() {
db, _ := sql.Open("sqlite3", "app.db")
app := framework.NewApp(framework.WithDB(db), framework.WithMCP()) // WithMCP serves the tools at /mcp
// CRUD is auto-on when a DB is set (CRUD *bool: nil = auto).
app.Entity("posts", framework.EntityConfig{
Exposure: &framework.ExposureConfig{
Public: true, // anonymous read AND write; omit it and CRUD requires a session (secure by default)
MCP: true, // emit posts_list/get/create/update/delete MCP tools
},
Fields: []schema.Field{{Name: "title", Type: schema.String, Required: true}},
})
log.Fatal(app.Start(":8080")) // GET/POST /posts, /openapi.json, MCP: all live
}That's the whole program. No config files, no codegen step, no registration
boilerplate. Add entities-as-JSON, batteries, the UI runtime, or the
generator when you need them. For how a flat app grows into
internal/<domain>/ as boundaries appear, see
project structure.
The full app shape: server-rendered screens with SEO, an owner-scoped
entity API, MCP for agents, and login + sessions, in one binary. A screen
is Go too: Render returns HTML, and a small JS runtime hydrates it in
place, with no React or Vue on the client:
package main
import (
"database/sql"
"log"
"github.com/DonaldMurillo/gofastr/battery/auth"
"github.com/DonaldMurillo/gofastr/core-ui/app"
"github.com/DonaldMurillo/gofastr/core-ui/html"
"github.com/DonaldMurillo/gofastr/core/render"
"github.com/DonaldMurillo/gofastr/core/schema"
"github.com/DonaldMurillo/gofastr/framework"
"github.com/DonaldMurillo/gofastr/framework/uihost"
_ "github.com/DonaldMurillo/gofastr/sqlite/stdlib"
)
// A screen is plain Go: Render returns server-rendered HTML.
type HomeScreen struct{}
func (s *HomeScreen) ScreenTitle() string { return "Notes" }
func (s *HomeScreen) Render() render.HTML {
return html.Heading(html.HeadingConfig{Level: 1}, render.Text("My notes"))
}
func main() {
db, _ := sql.Open("sqlite3", "notes.db")
// Server-rendered screens. Each also serves an auto llm.md.
ui := app.NewApp("Notes")
ui.Register("/", &HomeScreen{}, nil)
// SEO for those pages.
host := uihost.New(ui,
uihost.WithDescription("A tiny notes app."),
uihost.WithOpenGraph(uihost.OG{Title: "Notes", Type: "website"}),
uihost.WithSitemap(uihost.SitemapConfig{BaseURL: "https://notes.example"}),
)
// MCP for agents.
fwApp := framework.NewUIHostApp(host,
framework.WithDB(db),
framework.WithAPIPrefix("/api"),
framework.WithMCP(),
)
// Scope.OwnerField scopes rows per user: anonymous → 401, cross-user → 404.
fwApp.Entity("notes", framework.EntityConfig{
Scope: &framework.ScopeConfig{OwnerField: "user_id"},
Exposure: &framework.ExposureConfig{MCP: true},
Fields: []schema.Field{{Name: "title", Type: schema.String, Required: true}},
})
// Login + sessions.
authMgr := auth.New(auth.AuthConfig{
DevMode: true, // dev only: mints a per-process JWT secret; set JWTSecret in prod
UserStore: auth.NewEntityUserStore(db, "auth_users"),
SessionStore: auth.NewEntitySessionStore(db, "auth_sessions"),
})
authMgr.Use(auth.NewCorePlugin())
if err := authMgr.Init(fwApp); err != nil {
log.Fatal(err)
}
fwApp.Use(auth.SessionMiddleware(authMgr))
log.Fatal(fwApp.Start(":8080"))
}GET / is the rendered screen. Anonymous GET /api/notes answers 401:
OwnerField scopes rows per user, and auto-CRUD requires a session
unless the entity is Public. /auth/register and /auth/login come
from the auth battery, and the MCP tools at /mcp respect the same
owner scope as the REST API.
To work on the framework itself, or run the examples:
git clone https://github.com/DonaldMurillo/gofastr.git
cd gofastr
go test ./... # SQLite; the Postgres halves skip without TEST_POSTGRES_DSN
go run ./cmd/gofastr --help # CLI overview
go run ./examples/blog # minimal blog with auto-CRUD on SQLitePostgres setup and the race pass are under Contributing. Linked Git worktrees of the same app each get their own port and database path, so two coding agents can run side by side (isolation).
The module dependency and the installed CLI are versioned independently;
keep them on the same release. gofastr upgrade reads your go.mod, lists
every migration note between your version and the target (breaking changes
are marked in the release notes),
and points at the affected lines in your code (--apply runs the steps).
Manual steps and the full guide:
upgrading, or gofastr docs upgrading.
The framework emits routes, validation, migrations, pagination, uploads,
the spec, and agent tools from one declaration
(app.Entity in Go, or an entities: entry in a blueprint).
Declarations are optional: core/ routes and hand-written screens run
without them. A declaration grows the same way it starts, with fields,
enums, relations, soft delete, and a state machine on the status:
app.Entity("posts", framework.EntityConfig{
Scope: &framework.ScopeConfig{SoftDelete: true},
Fields: []schema.Field{
{Name: "title", Type: schema.String, Required: true},
{Name: "body", Type: schema.Text},
{Name: "status", Type: schema.Enum,
Values: []string{"draft", "published"}, Default: "draft"},
{Name: "author_id", Type: schema.Relation, To: "users"},
},
States: &framework.StatesConfig{
Field: "status",
Initial: []string{"draft"},
Transitions: []framework.Transition{
{Key: "publish", From: []string{"draft"}, To: "published"},
},
},
Exposure: &framework.ExposureConfig{MCP: true},
})| Output | Auto-generated |
|---|---|
| HTTP | GET / POST /posts, GET / PUT / PATCH / DELETE /posts/{id} |
| Batch endpoints | POST / PATCH / DELETE /posts/_batch: atomic; one tx for all items |
| SSE stream | GET /posts/_events: entity.created/updated/deleted, scoped per tenant |
| Filtering | ?status=published&views_gte=10&sort=-created_at&page=2 |
| Eager loading | ?include=author.profile,comments: flat or nested, validated against the registry |
| Cursor paging | ?cursor=&limit=50: keyset paging (cursor-pagination) |
| Multipart upload | multipart/form-data on Image/File fields → streamed through WithFileStorage |
| Validation | Required, unique, enum, min/max, regex pattern, multi-tenant scope |
| Migrations | Versioned runner with drift + dirty-state guards; declarative incremental generation (migrations) |
| FK constraints | BelongsTo relations emit FOREIGN KEY clauses; AutoMigrate topo-sorts tables |
| Transactions | Create/Update/Delete + hooks share one tx; TxFromContext(ctx) exposes it |
| OpenAPI 3 | /openapi.json plus a spec-viewer page at /api/docs/ |
| MCP | posts_list, posts_get, posts_create, posts_update, posts_delete, posts_publish |
| State moves | POST /posts/{id}/transitions/publish: the status changes only through a move, on every write path; one call per move in each client (states) |
| Soft delete | deleted_at column + automatic filter |
| Multi-tenant | tenant_id column + automatic scope from request context |
| Hooks | BeforeCreate, AfterUpdate, etc. for custom behaviour |
| Custom routes | EntityConfig.Endpoints with optional MCP exposure |
| Client SDKs | gofastr generate sdk: a Go module + JS/TS clients, with a live docs site (sdk) |
| Customer CLI | gofastr generate cli: a branded terminal client for your customers, scoped API-token auth |
Try all of it against a running server: examples/api-tour
is the curl tour, covering eager loading, cursor paging, atomic batch, SSE,
uploads, and sparse updates. Hooks run inside the write's transaction
(hooks-and-transactions).
- Two layers. A small
core/of stdlib-first primitives sits under an opinionatedframework/. Use the framework for the common path; drop to core and write plainnet/httpwhen it's in your way. (The one external touchpoint iscore/middleware/tracing.go, which pulls in OpenTelemetry; the rest ofcore/is stdlib-only.) - Server-rendered UI, hydrated in place. Screens are Go:
Renderreturns HTML and the server sends the full page. A small JS runtime attaches to it. In-page changes like sort, paginate, or add-a-row call the server and swap one region, and cross-page navigation swaps content client-side with a route cache, so there are no hard refreshes. No React or Vue on the client, and no router code for you to write. - The interactive layer keeps no server state. Sessions are signed tokens, so any replica serves any request. Updates pull first: client signals, then RPC, then polling. SSE push is reserved for presence and collaboration (
reactivity.md). - Security scopes live in the declaration, fail-closed.
owner_fieldmakes auto-CRUD per-user (anonymous → 401, cross-user → 404),access:gates operations behind RBAC permissions (403),multi_tenantscopes by tenant, andgofastr validateflags PII-shaped fields (email, phone, address, …) exposed without any of them. The MCP tools respect the same scopes as the REST routes. - You own the output. The generated code is normal Go you read, debug, commit, edit, and compose from your own
main. Registration is ordinary Go in the generated files; no reflection discovers your entities, and no platform sits between your binary and your server. - Batteries are separate packages. Auth, cache, email, queue, search, storage sit behind narrow interfaces; swap any one without forking.
- The framework checks whether you're still using it well.
gofastr verifyruns the 78 rules of the contract catalog (routing, permissions, security, rendering, accessibility) and measures semantic coverage: not "did this line run" but "did a request ever reach this route, did this permission ever get evaluated". Error-severity findings fail the run (--strictmakes warnings fail too), with per-line waivers and a baseline mode for existing codebases. See contracts. - A blueprint scaffolds the whole app when you want a head start. One
gofastr.ymlgenerates the screens and the API in one pass; then it's plain Go you own and edit, and the running app never needs the blueprint again (blueprint tutorial).
| Directory | What it is | Depend on it when… |
|---|---|---|
core/ |
Stdlib-only primitives: router, query, schema, render, mcp, openapi, migrate. Each usable on its own. | you want plain Go building blocks, no framework. |
framework/ |
The opinionated entity layer (App, EntityConfig, CRUD, hooks, migrations). A thin facade re-exporting its focused runtime subpackages. |
you want one declaration → SQL + REST + OpenAPI + MCP. |
core-ui/ |
Server-driven UI runtime: html primitives, patterns, widget islands, signals, localdb (IndexedDB databases declared in Go, records kept in the visitor's browser), the vanilla-JS runtime. Independently usable. |
you're rendering HTML from Go. |
battery/ |
Opt-in infrastructure: admin, auth, cache, email, semantic, log, notify, print, queue, relay, rtc, search, setup, storage, webhook, desktop (experimental: the app in the OS WebView, pure Go), and service (experimental: the app binary as a per-user OS service with a browser UI on loopback). Each behind a small interface. | you need a real subsystem; import only the ones you use. |
cmd/gofastr |
The CLI: init, generate, pack (lossy app→blueprint snapshot), migrate, build, dev, verify, docs, and more. |
you're scaffolding, generating, or checking code. |
kiln |
Experimental agent build-mode runtime (mutate an in-memory IR over HTTP). | you're driving the app from an agent. |
examples/ |
Runnable reference apps: the meridian blueprint flagship (a SaaS billing console + marketing site), the ecommerce blueprint pipeline, plus blog, api-tour, spa, and the docs site. |
you want to see it wired end-to-end. |
You import framework and the batteries you opt into, not each of its subpackages. The
subpackage split is an internal seam (see framework/ARCHITECTURE.md); the
public API is framework.X plus the batteries you reach for.
In production:
- Barcode & QR Code Maker: a live tool, no signup required, to generate and read barcodes and QR codes (QR, EAN-13, UPC-A, Code 128, Data Matrix, and more) as PNG, SVG, or PDF, with CSV/Excel batch export to a ZIP, a REST API, and an MCP server. Built and running on GoFastr.
The framework also runs on itself: examples/site is the
docs site and component gallery, examples/meridian is a
blueprint-generated SaaS console + marketing site edited by hand ever since,
and examples/ecommerce is still owned by the generator
and regenerated on every test run. All three import the same framework,
core-ui, and batteries a user app does, and each carries an end-to-end
suite. External production adopters are the part still ahead of us; see
Project status.
Every doc is embedded into the gofastr binary: gofastr docs browses them
offline, the docs site serves them
rendered with live component demos, and the framework_docs_* MCP tools
(framework.WithMCPTools(mcptools.Register) from framework/docs/mcptools) expose them to
agents connected to a running app. The
docs index is the browsable entry point.
Start with:
- The gofastr CLI: every subcommand mapped to its doc
- Blueprint tutorial: generate a whole app from one file, then own the code
- Entity declarations: the declaration schema reference
- UI getting started, the UI components index, and the runtime contract
- Authentication and access control
- Contracts: the
gofastr verifyrule catalog and semantic coverage - Deployment and horizontal scaling
- Agent-ready: llms.txt, the agent card, and MCP discovery
- Agent host: the chat-host surface: RoleAgent, MCP App widgets, host theme
- WebMCP bridge (experimental): expose server-declared tools to in-browser agents via the page's
modelContext - WebRTC rooms: signaling-only rooms plus the
__gofastr.connectRoombrowser module; media flows peer to peer or through your TURN relay, never through the Go process
GoFastr is pre-1.0 and explicitly not stable. Pin a version
(go get …@v0.x.y); a v1.0.0 tag will mark the stability promise.
- The
core/primitives are usable and tested in isolation. - The
framework/entity layer handles SQLite + Postgres CRUD apps today. core-ui/changes fastest. Its exported APIs follow the same deprecation policy as the rest of the tree: deprecate first, keep the old shape for at least one minor release. Expect that window to be exercised more often here than anywhere else before v1.- The CLI binary bundles a pure-Go SQLite driver (
modernc.org/sqlite, registered assqlite3bygofastr/sqlite/stdlib), so it builds withCGO_ENABLED=0. To run migrations against Postgres, build a custom binary that imports your driver of choice.
This is a personal project first, a way to practice building something large alongside AI. A few things I wanted to dig into:
- Solidify my web-tech foundations by rebuilding the stack from the socket up, so the fundamentals stop feeling like magic.
- Attack UI generation from a different angle. My background is in Node; I wanted to see what server-rendered, server-driven UI looks like with the heavy client framework off the table and the markup generated in a compiled language instead.
- Work in a compiled language, where a compiler catches the mistakes Node surfaces at runtime, in production.
- Skip the convention-vs-configuration false choice. Your own framework can have opinionated defaults and a hatch down to plain stdlib code in the same app.
- Build something large, fun, and open source with AI. Most of this repo was written alongside coding agents; the workflow itself is part of the experiment.
This repo is a personal research tree at the moment. Issues and PRs are welcome but expect strong opinions about scope: the goal is a framework an AI agent can drive end-to-end, not a kitchen-sink CMS.
Before pushing, the .githooks/pre-push gate re-runs the deterministic CI test sweep (when a commit skipped it), module-integrity checks, and govulncheck. The race pass is separate. Run make test-race for that. Enable hooks once with:
git config core.hooksPath .githooksThe framework's tests fan over both SQLite and Postgres. With Docker running, every dialect-aware test runs on both engines automatically:
make test # SQLite only, fast
make test-pg # both dialects against the docker-compose Postgres
make test-pg-env # both dialects, points at TEST_POSTGRES_DSN
make test-race # race detector across the whole repoEach Postgres test gets its own schema for isolation; the container is
shared across the whole go test invocation so cold-start is amortised.
MIT: free to use, modify, and distribute, including commercially, provided the copyright notice and license text are preserved.